Tutorials¶
Creating Macros¶
Macros are a flexible way to define new language constructs within the language. This tutorial implements a for-each loop as an example.
First, let’s define the form of our for-each loop:
(for target in vector expression)
target is the name of the symbol, vector is the
sequence we’re iterating through, and expression being the
actions performed for every iteration.
We can use the macro function to define this form:
(macro for [target in vector expression]
(do (setn vector (eval vector))
(setn index 0)
(setn final (len vector))
(loop (when (= index final) (break))
(setr target (at index vector))
(eval expression)
(setn index (+ index 1)))))
Analysing this example, two procedures are to be observerd,
specifically, the use of setr and eval. First, the
vector that is passed to the macro contains unevaluated
expressions that first have to be evaluated using eval. As
described by the form that we’ve written earlier, target is
the name of the symbol that we’re assigning to, and as such, we’ll
have to use setr instead of setn to make sure that
we’re not binding using target literally. We then evaluate the
expression literal, which has access to the value bound to
target.
Let’s test it out on the REPL:
> (macro for [target in vector expression]
| (do (setn vector (eval vector))
| (setn index 0)
| (setn final (len vector))
| (loop (when (= index final) (break))
| (setr target (at index vector))
| (eval expression)
| (setn index (+ index 1)))))
for
> (for x in [1 2 3 4 5]
| (print (* x x)))
1
4
9
16
25
:NIL
The newly defined for macro is able to iterate through the
vector, binding each number to x before evaluating the
expression.
Note: Macros create their own closure, and symbols bound within them are inaccessible after evaluation.
Modifying the macro to return the expression evaluated last is left as an exercise for the reader.