Module System¶
Modules facilitate the division of programs into separate files. In Juvix, this is achieved through top modules and local modules. Top modules are defined at the file's top, while local modules are nested within another module. Local modules also serve to organize different scopes within a file.
Syntax of module
declaration¶
The syntax for defining a module is:
module <name>;
<body>
end;
In this syntax:
-
<name>
represents the module's name. -
<body>
is a sequence of Juvix statements (traits, data types, functions, etc.). -
The
end
keyword is used to close the module definition.
Note
The end
keyword is optional if the module definition is the last statement in the file.
For instance, to define a module named Data.List
, we write:
module Data.List;
<body>
Top module naming convention¶
Top modules that belong to a project must follow a naming
convention. That is, if dir
is the root of a project, then the module in
dir/Data/List.juvix
must be named Data.List
.
Import and open statements¶
In order to access the definitions from another modules we use an
import statement. To import some module named Data.List
we will write
import Data.List;
Now, we can access the definitions in the imported module using qualified
names. E.g., Data.List.sort
.
It is possible to import modules and give them a more convenient name thus:
import Data.List as List;
Open statements¶
If we want to access the contents of a module without the need to qualify the names, we use an open statement. The syntax is as follows:
open Data.List;
Now we can simply write sort
.
It is important to remember that when we open a module, that module must be in scope, i.e., it must either be imported or defined as a local module.
Since importing and opening a module is done often, there is special syntax for that. The following statement:
import Data.List open;
Is equivalent to this:
import Data.List;
open Data.List;
In general, we can combine any import statement and open statement that refer to
the same module. The syntax is <import statement> <open statement without module
name>
.
When opening a module, if we want to open an explicit subset of its definitions,
we must use the using
keyword thus:
open Data.List using {List; sort; reverse};
We can also rename symbols in an open statement thus:
open Data.List using {List; sort as listSort; reverse as reverseList};
If we want to open all definitions of a module minus a subset, we
use the hiding
keyword thus:
open Data.List hiding {head; tail};
Sometimes you might want to open or hide the type constructors of a data type.
Recall that each data type defines a module containing names linked to its type
constructors. For example, if you want to hide the type constructors of a data
type, you must use the hiding
keyword and module keyword as follows:
import Stdlib.Prelude open hiding {module List};
Reexport modules¶
All opened definitions are available under the current module, but
they are not exported by default. Meaning that if another module imports the current
module, it will only be able to access the definitions defined there but not
those which have been opened. If we want opened definitions to be exported, we
must use the public
keyword thus:
module Prelude;
import Data.List open public;
Now, from another module we can access definitions in Data.List
through the
Prelude
module.
module MyModule;
import Prelude open;
-- List, sort, reverse, etc. are now in scope
Local modules¶
Juvix modules have a hierarchical structure. So far we have discussed top level modules, which have a one-to-one correspondence with files in the filesystem. On the other hand, local modules are defined within another module. They can be useful to group definitions within a file.
The syntax for local modules is as follows:
module Path.To.TopModule;
module ModuleName;
<body>
end;
Note that local module names cannot contain the .
character.
After the definition of a local module, we can access its definitions by using qualified names. Local modules can be opened by open statements in the same way as top modules.
Local modules inherit the scope of the parent module. Some shadowing rules apply, and they probably follow your intuition:
- Opening or defining a symbol shadows inherited instances of that symbol.
- Opening a symbol does not shadow a defined instance of that symbol in the current module.
- Conversely, defining a symbol in the current module does not shadow an opened instance of that symbol.
As a consequence of 2 and 3, using a symbol that is both defined and opened locally will result in an ambiguity error. In order to solve that, the symbol that was opened, would need to be used qualified instead.