Section 6: Modules
Last updated
Was this helpful?
Last updated
Was this helpful?
As mentioned in , a Keli program is actually a set of modules.
In fact, each module actually corresponds to one and only one Keli source file.
In the following section, the term importer means the file that is importing other files, while importee means the file that is being imported. In Keli, a source file can be both importer and importee and the same time.
Unlike languages like Haskell, C or Java, there are no main module required for a Keli program. Keli follows the approaches of languages like Python and JavaScript, where the file being interpreted is the entry point of the program.
The Keli compiler shall warn the user if the module naming does not follows the PascalCase
convention.
By default, all declarations in a module are accessible by any other modules. To achieve encapsulation, Keli adopts a rather tolerant encapsulation mechanism, where the compiler would not trigger any compile error, but warnings instead.
To make a declaration invisible to other module, prefix the declaration with an underscore symbol. For example,
However, this does not means that the declarations above cannot be used by other modules, it will simply trigger compiler warning if the compiler found that other modules are using them.
To import a module, we should follow the grammar below:
module
.
import
(
filePath)
Examples as follows:
Note that filePath must end with .keli
, if not the compiler will raise an error, even though filePath points to a valid Keli source file.
In larger projects, using raw paths might be cumbersome, especially when refactoring the folder structures. In such situation, we can use aliased paths, so that the imports can be based on the project root.
Path aliases must be prefixed with the dollar sign.
Path aliases can be defined via the compiler command line arguments or by telling the compiler to load a config file.
Path aliases are defined based on the entry point of a program.
Example of using aliased paths:
Suppose we have the following folder structure:
Suppose the Main
module imports Shape
module:
And Shape
module imports Math
:
Then, the following outcome should be observed:
All declarations in Shape
will be visible to Main
except private declarations.
All declarations in Math
will be visible to Shape
.
However, no declarations in Math
will be visible to Main
, although Main
imported Shape
which imported Math
.
In a nutshell, the scoping rule are as follows:
Importee will only be visible to its corresponding direct importer. In another words, the importees of importee will be not be visible to the importer.
This section shall define what is considered import conflicts in Keli, how the compiler should behave in such cases. The term identical will be defined first before defining what is a conflict.
Mono-nominal declarations means any declarations that can be identified by using only a single string token. In Keli, any non-function declaration are considered mono-nominal.
Two mono-nominal declarations are considered identical if their identifier (which appears on the left of the assignment operator) are lexically equal.
For example, all of the following mono-nominal declarations are considered identical:
Poly-nominal declarations are any declarations that cannot be identified by using only a single string token. In Keli, all function declarations are considered poly-nominal, this due to:
Multiple dispatch, it means two or more functions can bear the same identifiers without introducing compile error.
Function identifiers can be spitted into different parts, like the message syntax in Smalltalk.
Two function declarations, say X and Y, are considered identical if all of the following criteria is observed:
The arguments length of X is equals to the arguments length of Y.
The identifiers of X have the same length with the identifiers of Y (implementation does not need to include this checking, because criterion 1 implies this criterion.
The first identifier of X is lexcially equivalent to the first identifier of Y, and so on and so forth for the rest of the identifiers.
The type annotation of the first argument of X is identical to the type annotation of the first argument of Y, and so on and so forth for the rest of the arguments.
The following code snippets demonstrates identical function declarations.
The following code snippets demonstrates non-identical function declarations, with the reason commented on top of each snippet.
In Keli, there are there kinds of type annotation (TA), namely, simple TA, compound TA and bounded type variables.
STA are those that consist of only a single string token identifier. For example, Int
, String
, etc. Two STA are considered identical is their identifier is lexically equal.
CTA are those that does not only consist of a string token identifier, but also contains inner type annotations. Two CTA are considered identical if their identifier is lexcially equal to each other and their inner types are identical.
The following code snippets demonstrate identical CTA:
The following code snippets demonstrate non-identical CTA:
BTV is primarily used for declaring generic functions. Two BTV are considered identical if their corresponding constraint are identical.
In the following snippet, A
and B
are identical BTV despite their name difference, because their constraint is identical.
However, in the following snippet, A
and A
are considered non-identical BTV because their constraint are different, albeit having identical identifier.
Import conflicts happen when an importer X, where its importees contain identical declarations when compared to each other.
For example, suppose there are two modules as follows:
And a module C
with imported both A
and B
as follows:
When running C
as entry point, no compiler error or warning will be shown, although A
and B
contains identical declarations, which is the plus
function.
However, if a module D
imported both A
and B
and uses the plus
function as follows:
When running D
as entry point, the compiler will throw an error saying:
Import conflicts are sometimes unavoidable, albeit Keli supports multiple dispatch. To resolute conflicts, we can use the .using
magic function.
Suppose we have the following files:
When we imports those files into another file, say Main.keli
, no compile error would be introduced.
However, when we attempts to use the square
function, which is defined in both MathV1
and MathV2
with the same parameter types, we will get a compile error:
To resolve the error above, we can use the .using
function as follows:
Warning
In the situation of naming conflicts, one shouldn't use conflict resolution straight away, but should properly investigate if there are code duplication, because it is very rare for two functions to bear the same name and the same parameter type but serves a different purpose.
Thus, despite using conflict resolution, one could choose to:
Remove the duplicated function
Rename the function that causes conflicts
In this section, we will discuss about the various types of restrictions imposed by the Keli module system.
Any Keli source file cannot import two or more modules that have the same name, even though they sits in different directory.
For example, suppose we have the following file structure:
And in Main.keli
:
Will result in a compile error, because both imported modules have the same name, which is Math.keli
.
Cyclical imports are not allowed, i.e., if a module A imports module B, then B cannot imports A, as doing so will form a cyclic graph.
where filePath is any valid , be it relative path or absolute path.
This section shall define how are considered identical in Keli.
Suppose C
imported A
and B
, but both A
and B
contains , say XA(declared in A) and XB(declared in B), the compiler should never give any kind of warning or errors, unless an identifier XC
which is identical to XA
and XB
is used in C
.