Modules are a programming language construction that allows us to package related definitions together. A canonical example of a module is a data type and associated operations over it (e.g. stacks or queues). The rest of the program can access these definitions in a regular and abstract way, providing maintainability, reusability and safety.
For a concrete example, we could create a module that packages a type that represents amounts in a particular currency together with functions that manipulate these amounts: constants, addition, subtraction, etc. A piece of code that uses this module can be agnostic concerning how the type is actually represented inside the module: it's abstract.
Modules are introduced using the
module keyword. For example, the
following code defines a module
EURO that packages together a type,
t, together with an operation
add that sums two values of
the given currency, as well as constants for zero and one.
We can access a module's components by using the
. operator. Let's
suppose that our storage keeps a value in euros using the previously
EURO. Then, we can write a
main entry point that
increments the storage value each time it is called.
In principle, we could change the implementation of
having to change the
storage type or the function
example, if we decide later that we should support manipulating
negative values, we could change
EURO as follows:
Notice that the code in
main still works, and no change is
needed. Abstraction accomplished!
⚠️ Please note that code using the module
EUROmight still break the abstraction if it directly uses the underlying representation of
EURO.t. Client code should always try to respect the interface provided by the module, and not make assumptions on its current underlying representation (e.g.
EURO.tis an alias of
Modules can be nested, which means that we can define a module inside
another module. Let's see how that works, and define a variant of
EURO in which the constants are all grouped inside using a sub-module.
To access nested modules we simply apply the accessor operator more than once:
Modules also allow us to separate our code in different files: when we import a file, we obtain a module encapsulating all the definitions in it. This will become very handy for organizing large contracts, as we can divide it into different files, and the module system keeps the naming space clean.
Generally, we will take a set of definitions that can be naturally grouped by functionality, and put them together in a separate file.
For example, in PascaLIGO, we can create a file
Later, in another file, we can import
imported.ligo as a module, and
use its definitions. For example, we could create a
that imports all definitions from
imported.ligo as the module
We can compile the file that uses the
#import statement directly,
without having to mention the imported file.
LIGO supports module aliases, that is, modules that work as synonyms to other (previously defined) modules. This feature can be useful if we could implement a module using a previously defined one, but in the future, we might need to change it.