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.