Modules provide a means of avoiding name collisions between nodes. A
module is a namespace which contains all global nodes, including
meta-nodes, created in it. A node with identifier x
in a module m1
is distinct from a node with the same identifier x
in another module
m2
.
Each new node, that is created as a result of processing a declaration
in the source file, is added to the current module. Initially the
current module is a nameless init
module until it is changed
explicitly.
The current module can be changed using the special /module
operator,
which takes the identifier of the module as its only argument. If
there is no such module a new module is created.
Example.
# Change to module with identifier `mod1` /module(mod1) # Nodes `a` and `b` added to `mod1` a -> b # Change to module with identifier `mod2` /module(mod2) # Nodes `a` and `b` added to `mod2` # Distinct nodes from nodes `a` and `b` in `mod1` a -> b
Module identifiers reside in a different namespace from node identifiers, thus there is no risk of collision between a node and module with the same identifier, unless a pseudo-node for the module is added to the module containing the node. |
Modules reside in a single global, flat namespace. Hierarchical
relations between modules have to be faked with a separator such as
|
There are two ways to reference a node in a another module, different from the current module. One way is to create a pseudo-node for the module in the current module. Nodes in the module can then be referenced as subnodes of the module’s pseudo-node.
The special /use
operator creates pseudo-nodes for the modules
passed as arguments. The pseudo-nodes are created with the same
identifiers as the modules.
Module pseudo-nodes are referred to as such, since syntactically they are the same as any other node, however the value of a module pseudo-node cannot be referenced nor can bindings involving it be established. |
Syntax.
/use(mod1, mod2, ...)
Creating a pseudo node in a module, which contains a node with same identifier as the pseudo-node, results in a compilation error. |
Nodes from the used modules can then be referenced as subnodes of the module pseudo nodes.
Example.
/module(mod1) a -> b /module(mod2) /use(mod1) # Reference node `b` from module `mod1` mod1.b -> b x -> mod1.b
Meta-nodes from a different module can be also referenced as subnodes of the module pseudo-node.
Example.
/module(mod1) add(x, y) : x + y /module(mod2) /use(mod1) # Use the `add` meta-node from module `mod1` mod1.add(a, b) -> c
Nodes referenced from other modules, can appear both as dependencies or observers of bindings. |
Referencing a subnode of a module pseudo-node does not result in the automatic creation of a node in that module. Referencing a node that does not exist in the module results in a compilation error. |
A pseudo-node with a different identifier, from the identifier of the
module, can be created using the special /use-as
operator. This is
useful for when the module identifier is too long to type out
repeatedly, or there is already a node, in the current module, with
the same identifier.
the /use-as
operator takes two arguments, the identifier of the
module and the name of the pseudo-node to create in the current
module:
/use-as(module-name, pseudo-node-name)
The above examples can be rewritten using /use-as
declarations:
Example.
/module(mod1) a -> b /module(mod2) /use-as(mod1, m1) # Reference node `b` from module `mod1` m1.b -> b x -> m1.b
Example.
/module(mod1) add(x, y) : x + y /module(mod2) /use-as(mod1, m1) # Use the `add` meta-node from module `mod1` m1.add(a, b) -> c
The second approach to referencing a node, residing in another module,
is to add it directly to the current module. With this approach there
is no need to reference the node as a subnode of a module
pseudo-node. This is referred to as importing the node and is
achieved using the /import
operator.
The /import
operator adds the identifiers of nodes, residing in
another module, to the current module. The result is that the node can
be referenced directly by its identifier, as though it were declared
in the current module.
The /import
operator has two forms:
Syntax.
# Short form: Import all nodes exported from `module` /import(module) # Long form: Import only the nodes listed in the arguments after the # module identifier. /import(module, node1, node2, ...)
Example: Long form.
/module(mod1) a -> b /module(mod2) # Import node `b` from `mod1` /import(mod1, b) # Node `b` is the same `b` as in `mod1` b -> a x -> b
The short form only imports those nodes which are explicitly
exported from the module. Nodes are explicitly exported from the
current module with the /export
operator which simply takes the
identifiers of the nodes to export as arguments.
Syntax.
/export(node1, node2, ...)
Each /export
declaration adds (does not replace) nodes, that are in
the arguments, to the exported nodes of the current module.
Example: /export
and short form /import
.
/module(mod1) a -> b # Export node `b` /export(b) /module(mod2) # Import all nodes exported from `mod1` /import(mod1) # Node `b` is the same `b` as in `mod1` b -> a x -> b
Meta-node Example: /export
and short form /import
.
/module(mod1) add(x, y) : x + y /export(add) /module(mod2) /import(mod1) # Use the `add` meta-node from module `mod1` add(a, b) -> c
A side effect of /import
is that if the identifier of an imported
node, whether imported by the long or short form of /import
, is
registered as an infix operator in the module, from which it is being
imported, it’s entry in the module’s operator table is copied over to
the current module. This allows the operator to be placed in infix
position, in the current module.
All nodes in the |
It may be necessary to reference a node in another module without
creating a pseudo-node, for its module, and without importing it in
the current module. The special /in
operator directly references a
node in another module by the module and node identifiers.
Syntax.
/in(module-id, node-id)
The first argument is the identifier of the module containing the node, and the second argument is the identifier of the node.
|
Example: /in
Operator.
/module(mod1) a -> b /module(mod2) # Reference node `b` in `mod1` directly /in(mod1, b) -> a x -> /in(mod1, b)
Example: /in
Operator.
/module(mod1) add(x, y) : x + y /module(mod2) # Use the `add` meta-node from module `mod1` (/in(mod1, add))(a, b) -> c
Each module may register a number of node identifiers as infix operators. This means that those identifiers may appear in infix position in declarations parsed while the module is the current module. The module’s operator table stores the identifier, precedence and associativity of each infix operator. See Section 1.2, “Functors” for more information about infix operators, operator precedence and associativity.
Initially the operator table of each module contains a single entry which is the special entry for function application. The precedence of function application controls whether an expression is treated as the operator of a functor or the operand of an infix expression.
The precedence of function application is set to |
Example: Precedence of Function Application.
# If `.` has a higher precedence than function application, the # following is parsed to: ((. m1 add) a b) # If `.` has a lower precedence than function application, the # following is parsed to: (. m1 (add a b)) m1.add(a, b)
New infix operators can be registered using the special /operator
declaration. This declaration modifies the operator table of the
current module.
Syntax.
/operator(identifier, precedence [, (left | right)])
The first argument is the node identifier to register as an infix
operator. The second argument is the precedence as an integer
value. The third argument specifies the associativity. This is either
the identifier left
or right
for left or right
associativity. If the third argument is omitted, left associativity
is assumed.
The identifier does not have to name a node or meta-node that
exists at the time the |
An /operator
declaration adds an entry to the current module’s
operator table if it does not already contain an entry for the
identifier. If the table already contains an entry for the identifier,
the precedence and associativity values of the existing entry are
replaced with those given in the arguments to the /operator
declaration.
The precedence and associativity of all operators can be changed, with the exception of function application. |
When a node is imported into the current module and there is an entry, for the node’s identifier, in the operator table of the module, from which the node is being imported, the entry is copied over into the current module’s operator table, replacing any existing entries for the identifier.
Example: +
infix operator from core
module.
# Register `+` as infix operator /operator(+, 100, left) # Now `+` can appear in infix position a + b # It can also still appear in prefix position +(a, b)