Modules¶
To create a module, use the module
special form. All modules must start with a
module declaration which provides the module with a name, documentation string
and an exports vector. Module declarations are evaluated, so you must quote any exports.
Here is a simple example of a module:
(module
{:module 'my-module
:doc "Just for showing off modules."
:exports '[foo bar]}
(def foo-helper
(fn [x] (+ x 1)))
(def foo
(fn [x] (foo-helper (foo-helper x))))
(def bar
(fn [x] (foo (+ x 2)))))
To import a module, use import
:
(import my-module)
The content of a module is evaluated in a new scope that is identical to the scope of the module definition site. This means you you can use whatever has already been defined before the module was, and that definitions in the module don’t leak outside the module. The form returns the module value, which can be passed around just like any other value. This means that we can write functions which create modules:
(def make-taxes
(fn [VAT]
(module
{:module 'taxes
:doc "Calculates taxes."
:exports ['add-VAT]}
(def add-VAT (fn [x] (* (+ 1 VAT) x))))))
And then we could use this module with different values of VAT
:
(import (make-taxes 2/10))
;; We also need to import `prelude/io` to be able to use `print!`
(import (file-module! "prelude/io.rad") :unqualified)
(print! (string-append "100 EUR + VAT is: " (show (taxes/add-VAT 100))))
By default import
will add all the definitions of the module in
fully-qualified form, which means my-module/foo
and my-module/bar
(but not
my-module/foo-helper
) are in scope:
(print! (my-module/bar 0))
You can narrow down exactly which definitions you would like to import by including a list of atoms:
(import my-module ['foo])
Which would only import my-module/foo
. And you can also use a custom
qualifier:
(import my-module :as 'baz)
After which we can:
(print! (baz/bar 42))
So we could import our taxes module twive using different qualifiers:
(def taxes-fr (make-taxes 20/100))
(def taxes-de (make-taxes 19/100))
(import taxes-de :as 'de)
(import taxes-fr :as 'fr)
(print!
(string-append
"100 EUR + VAT is "
(show (de/add-VAT 100))
" EUR in Germany and "
(show (fr/add-VAT 100))
" EUR in France."))
If you really want to import all the definitions without a qualification, then
you can use the keyword :unqualified
like so:
(import my-module :unqualified)
And then we can:
(print! (foo 128))
Qualification and import lists can be combined like so:
(import my-module ['bar] :as 'useful)
Would only add useful/bar
to the current scope:
(print! (useful/bar 0))
When working at the REPL, the function file-module!
is also available, which
can create a module from a file. It assumes the file starts with a module
declaration, and is then equivalent to wrapping the contents of the file in
(module ...)
.
Modules can be passed around and manipulated because they are just dicts with
some metadata, and an :env
key containing a radicle environment.