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 [qtarget in qvector qexpression] ...)
target
, vector
, and expression
are prefixed
with q
as arguments to macros are passed literally as quoted
values that are not evaluated, giving the macro the ability to
selective unquote and evaluate expressions.
Let’s now define what happens within the macro:
(macro for [qtarget in qvector qexpression]
(do (setn target (unquote qtarget))
(setn vector (unquote qvector))
(setn expression (unquote qexpression))
(setn index 0)
(setn final (len vector))
(loop (when (= index final) (break))
(setr target (at index vector))
(eval expression)
(setn index (+ index 1)))))
The values being manipulated in the macro are first unquoted and bound
to names without their q
prefix:
(do (setn target (unquote qtarget))
(setn vector (unquote qvector))
(setn expression (unquote qexpression)))
Then, the looping logic is defined:
(setn index 0)
(setn final (len vector))
(loop (when (= index final) (break))
(setr target (at index vector))
(eval expression)
(setn index (+ index 1)))
Two things are to be observed here, specifically, the use of
setr
and eval
. As described by the form that we’ve
defined 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 to target
literally. We then evaluate the expression
literal, which then
has access to the value bound to target
.
Let’s test it out on the REPL:
> (macro for [qtarget in qvector qexpression]
| (do (setn target (unquote qtarget))
| (setn vector (unquote qvector))
| (setn expression (unquote qexpression))
| (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.