Section 6: Modules
As mentioned in Section 1, 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.
6.1 Entry Point
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.
6.2 Module naming convention
The Keli compiler shall warn the user if the module naming does not follows the PascalCase
convention.
6.3 Encapsulation
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.
6.4 Import syntax
6.4.1 Raw paths
To import a module, we should follow the grammar below:
module
.
import
(
filePath)
where filePath is any valid Unix file paths, be it relative path or absolute path.
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.
6.4.2 Aliased paths
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:
6.5 Scoping rule
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 toMain
except private declarations.All declarations in
Math
will be visible toShape
.However, no declarations in
Math
will be visible toMain
, althoughMain
importedShape
which importedMath
.
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.
6.6 Import conflicts
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.
6.6.1 Identical declarations
This section shall define how declarations are considered identical in Keli.
6.6.1.1 Mono-nominal declarations
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:
6.6.1.2 Poly-nominal declarations
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.
6.6.2 Identical type annotations
In Keli, there are there kinds of type annotation (TA), namely, simple TA, compound TA and bounded type variables.
6.6.2.1 Simple type annotation (STA)
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.
6.6.2.2 Compound type annotation (CTA)
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:
6.6.2.3 Bounded type variable (BTV)
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.
6.6.3 Definition of import conflicts
Import conflicts happen when an importer X, where its importees contain identical declarations when compared to each other.
6.6.4 Conflicts between importees
Suppose C
imported A
and B
, but both A
and B
contains identical public declarations, 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
.
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:
6.7 Conflict resolution
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
6.9 Restrictions
In this section, we will discuss about the various types of restrictions imposed by the Keli module system.
6.9.1 Importing module with same name
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
.
6.9.2 Cyclic imports
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.
Last updated