Module System and Import Statement
The Jolie module system is a mechanism that allows developers to share and reuse Jolie code from different files.
A Jolie module can be accessed and used through the import statement.
Definitions
The terminology for the module system differs among programming languages, thus it is useful to have a concrete definition before we delve into one for Jolie.
The Jolie module system is built upon three different components, namely: packages, modules, and symbols:
-
a symbol is a named definition declared in a file. Symbols are either
typedefinitions,interfacedefinitions, orservicedefinitions. As in other languages, the access to Jolie symbols can be restricted with the usual access modifiers prefixing of the symbol definition. Symbols without an access modifier are consideredpublicby default, whileprivateones are inaccessible from the importer; -
a module corresponds to a Jolie file and it contains a set of symbols. To make a Jolie module directly executable (e.g., runnable with the command
jolie myFile.ol), we need to have specified just oneservicewhich itself contains the blockmain { ... }. This single service (andmainprocedure) is the execution target/entry-point of the Jolie interpreter. Drawing a parallel, programmers define amainmethod in a Java class when that unit is the entry-point of execution: hence, the method is implemented in applications that define a specific execution flow, while libraries frequently omit amainmethod as they are modules imported and used by other projects. When there are more and one service per module, the interpreter prohibits direct the execution of modules and requires the definition of the--serviceparameter (explained below); -
a package is a directory that contains one or more Jolie modules. The
importmechanism uses the package name — i.e., the directory name — to traverse and locate the target module in the file system.
Import Statement
The syntax of the import statement is
from moduleSpecifier import importTarget_1 [as alias_1], ..., importTarget_n [as alias_n]
The importing module is defined by the moduleSpecifier. Users can use a dot . as path separator between packages and modules. Each identifier before the last one defines the packages where the importing module resides, while the last identifier represents either the importing package or the importing module name. If the last identifier is a package, Jolie module parser will attempt to look up for a module named main in particular package, or else it is a module.
Hence, the import fragment from A.B.C.d would read "look into folder A, then B, then C, check if d is a folder; if so, open module (the file) main in d, otherwise, open module (the file) d and import the following symbols (omitted)". When prefixed with a . module lookups are resolved from the location of the importing file, rather than the working directory from which the Jolie interpreter have been launched. E.g.,
from A import AInterface
// import AInterface definition from module A or A.main if A is a package.
from .B import BType as BLocalType
// import BType definition as BLocalType from module B (or B.main if B is a package) in the same package.
The second part of the import statement, importTarget, is a list of symbol names defined in the target module, with an optional qualified name to bind to the local execution environment denoted by using 'as' keyword. E.g.,
from package.module import symbolA // Absolute import
from .module import symbolB as localB // Relative import
Jolie also supports the importing of all the (public) symbols defined in a given module, with the wildcard keyword *, e.g., from myPackage.myModule import * — wildcard-imported symbols cannot be aliased, as they are imported in bulk.
Debugging the import system
The import statement executes in three stages:
- modules lookup;
- modules parsing;
- symbol binding.
For each import statement, the Jolie interpreter resolves the import as specified in the module path, performing a lookup to the importing source.
The source code of the imported target is then parsed and the symbol definitions are bound to the local execution environment.
Any errors that occur during the execution of an import statement, such as modules/symbols not found or illegal accesses of symbols terminate the execution of the interpreter.
Module Lookup strategies
During the module lookup, the Jolie interpreter uses the module path to identify and attempt to locate the imported module, resolving it following either an absolute or a relative (when the path is prefixed by a .) location strategy.
Absolute paths
For absolute paths, the interpreter performs the lookup within the directory of execution of the Jolie interpreter and the system-level directories where the modules of the Jolie standard library reside.
Let us illustrate the followed procedure to define the priorities of module lookup with a concrete example.
To do that, we first define some labels to represent the relevant parts of the module path used in the lookup:
- let
PATHrepresent the whole module path, e.g.,p1.p2.mod; - let
HEADrepresent the prefix of the path, e.g,,p1inp1.p2.mod; - let
TAILrepresent the suffix of the path, e.g.,p2.modinp1.p2.mod.
The first thing the absolute-path lookup does is looking for the module within the execution directory of the Jolie interpreter.
Hence, let WorkingDir represent the absolute path of the execution directory, we first lookup modules within WorkingDir in the following order:
- we look for the module following the nesting structure of the
PATHunder theWorkingDir; - if the above attempt fails, we look for a packaged version of Jolie services — which are files with the extension
.jap— contained within thelibsubdirectory of theWorkingDir. Specifically, we look for a.japfile namedHEAD.japwhich, if found, is inspected following the nesting structure of theTAIL; - if the above attempt fails, we apply the procedures 1. and 2. to the system-level directories (e.g., from the
packagesdirectory of the $JOLIE_HOME folder)
Relative paths
Relative paths (denoted by the . prefix) are resolved starting from the location of the importer.
Besides the first ., which signifies the usage of a location-relative resolution strategy, any following . indicates the traditional upward traversal of directories.
Let us label ImporterDir the directory of the importer module, then:
- a relative path import of the shape
.modwould look for the modulemodinsideImporterDir; - a relative path import of the shape
..modwould look for the modulemodin the parent directory ofImporterDir; - a relative path import of the shape
...modwould look for the modulemodin the parent of the parent directory ofImporterDir, and so on.
Reiterating the concept with the example path used in the previous section:
- the relative path
.p1.p2.modwould be resolved by looking for the modulemodwithin the nested directoriesImporterDir,p1, andp2; - a relative path import of the shape
..p1.p2.modwould be resolved by looking for the modulemodfrom the parent ofImporterDir, followed with the nested foldersp1andp2.