You've already seen some import syntax, but now you'll see all the options available.
If you recall Chapter 1, there was this example code:
(.require [library [lux (.except) [program (.only program)] ["" debug] [control ["" io]]]])
Here, we're importing the
(.except) option means locally import every definition exported by the
This allows usage of those definitions without having to give them the
library/lux. prefix, or even the
. shortcut prefix.
This may cause some issues if you import 2 definitions with the same name from different modules; or if you get a definition from one module, but then write your own definition with the same name in your code.
In those circumstances, the compiler will complain, saying that you can't re-define
X is the name of the definition.
Then, we import
library/lux/program, but we only import locally the
That is what the
.only option allows.
There is also a `.only` option which means locally import everything **except** the specified definitions_. You could use it like this: `[your_module (.except foo bar baz)]`
Finally, we import both the
In neither case do we import any of their definitions locally.
We also give both of those modules local aliases.
That is what that
"" syntax does.
.require macro recognizes that syntax for aliases and replaces the
 with the import name directly to the right.
This might not seem like a big deal, but the aliasing syntax allows you to give imports arbitrary names, so long as you take into account the substitutions that may happen.
So, for example:
It is also important to note that while imports can be nested for convenience, they don't have to be.
.require declaration could just as easily been written like this:
(.require [library/lux (.except)] [library/lux/program (.only program)] ["debug" library/lux/debug] ["io" library/lux/control/io])
You might also guess that
library was not imported as a module because it was neither given an alias, not had any definitions specified as local imports.
Any module-path fragments included in the import syntax without such options will not be imported and will simply be assumed to be part of the module-paths of the sub-modules specified under them.
It is also possible to have the
.require macro open interface implementations for you when importing the modules that contain them.
(.require [library [lux (.except) [data [collection ["" list (.use "::" functor monoid)]]]]])
The import above would locally import:
Here, we can also see some additional syntax for aliasing.
First of all, when opening implementations, aliasing syntax is used to determine the names of the local implementation imports.
 is replaced with the name of the implementation member.
 is bound to the name of the context of the import.
In this case, the implementations are coming from the
library/lux/data/collection/list module, so that is the context.
And since that module has been imported with the local alias
list, that is the name that replaces the
 in the aliasing syntax for the implementation imports.
And that is how we end up with the list of names I enumerated above.
 syntax for aliasing can also be used between modules, and not just when importing implementation members.
(.require [library [lux (.except) [data ["" collection (.only) ["/" list (.use "::" functor monoid)]]]]])
Would locally import:
The context between module imports corresponds to the closest ancestor path which has itself been aliased.
Non-aliased paths don't count as context.
(.require [library [lux (.except) ["" data [collection ["/" list (.use "::" functor monoid)]]]]])
Would locally import:
"ignore"syntax you may have noticed means do not import this module; just give it an alias I can refer to later as a context.
I should also note that you can both locally import definitions and open implementations as parts of the same module import.
(.require [library [lux (.except) [data [collection ["" list (.only repeated size) (.use "::" monad)]]]]])
Another important feature of module imports is relative addressing, which comes in 2 flavors.
For the first one, suppose you have the following directory structure:
program foo bar baz quux test foo bar baz quux
And you're writing code in the
You can import other modules in the hierarchy like this:
... In program/foo/baz (.require [library [lux (.except)]] ["" /quux] ... program/foo/baz/quux, aliased as /quux ["" //bar] ... program/foo/bar, aliased as //bar ["" ///] ... program, aliased as /// )
A single forward slash (
/) signifies "this module" in the hierarchy, so anything after the forward slash is assumed to be under this module.
Two forward slashes (
//) signify "the module above", and any forward slash after that allows you to go further up the hierarchy.
In the case of
program, it's enough to just specify three forward slashes (
///) for the
.require macro to know which module you're referring to.
You can think about it like this:
Also, this relative path syntax can be nested, like so:
... In program/foo/baz (.require [library [lux (.except)]] [/ ["" quux]] ... program/foo/baz/quux, aliased as quux [// ["" bar] ... program/foo/bar, aliased as bar ] ["" ///] ... program, aliased as /// )
... In program/foo/baz (.require [library [lux (.except)]] [/ ["" quux] ... program/foo/baz/quux, aliased as quux [// ["" bar] ... program/foo/bar, aliased as bar ["program" //] ... program, aliased as program ]])
You may have noticed that when importing
program, we went from
That is because, since it's nested under another
//, it's relative to
program/foo instead of
program/foo/baz, so only 1 step up is necessary instead of the 2 steps a
/// would provide.
For the second way to do relative imports, you can see this example:
... In program/foo/baz (.require [library [lux (.except)]] [\\test ["" /] ... test/foo/baz, aliased as / ] ... Alternatively ["" \\test] ... test/foo/baz, aliased as \\test ... Or [\\ [\test ["" /] ... test/foo/baz, aliased as / ]] )
The backslash (
\) works in the reverse direction to the forward slash (
If the forward slash allows you append paths to the back, and to move up the hierarchy from the end; then the backslash allows you to append paths to the front, and the move down the hierarchy from the beginning.
Why would you want such a thing?
Because it allows you to easily establish parallel hierarchies of modules, which is a useful way to separate orthogonal aspects of your program (like the
test hierarchies in our example).
Then, by using this relative syntax, you can refer to one hierarchy from another in an easy way.