Skip to main content

Macros

Macros are the most powerful feature of the language (both lisp and Scheme). Macros allows you to add new syntax to the language, it also allows making simpler code and reduce boilerplate.

Macros works like a function, but the arguments of the macro are not evaluated before passing the value to the function. But instead the code of the arguments are passed to the macro, the macro then can manipulate the code and return new code that is then evaluated. This happens during expansion time before evaluation even happen.

Lisp Macros

Some scheme implementation supports lisp macros, so I will describe them first.

If you have code like this

(foo (+ 1 2))

And foo is a function, the (+ 1 2) will be evaluated and 3 will be passed to the function. But if foo is a macro, the data structure (+ 1 2) will be passed to the macro.

To define a macro, you use usually use define-macro syntax. Lisp macros in Scheme are not standard, so the syntax may change depending on Scheme implementation.

(define-macro (foo expr)
  (case (car expr)
    ((+) (set-car! expr '-))
    ((-) (set-car! expr '+)))
  expr)

This macro swap first element of the expression passed as argument. If you pass sum of two numbers It will subtract the values:

(foo (+ 1 2))
;; ==> -1

if you use minus symbol it will add up the numbers:

(foo (- 1 2))
;; ==> 3

Instead of modification of existing code you can also create new list:

(define-macro (foo expr)
  (list (case (car expr) ((+) '-) ((-) '+))
        (cadr expr)
        (caddr expr)))

It will work similarly but only with two numbers:

(foo (- 1 2))
;; ==> 3
(foo (+ 1 2))
;; ==> -1

Macroexpand

Some scheme implementation have function macroexpand that allow to inspect the result expression returned by the macro.

(macroexpand '(foo (+ 1 2)))
;; ==> (- 1 2)

Macros can be nested, so one expression can expand into something you don't expect to see. For this you have a function called macroexpand-1 that should expand macro one time. Which in turn should expand just your macro.

tip

Before you read the next section, it's recommended to read about quasiquote syntax first.

New Control Flow Constructs

With macros, you can define new control flow (e.g. like if statements). Here is an example of when macro that is part of R7RS standard.

(define-macro (when test . body)
  `(if ,test
       (begin
         ,@body)))

(let ((x 0))
  (when (zero? x)
    (display "x")
    (display " = ")
    (display "zero")
    (newline)))
;; ==> x = zero

As you probably already know to use multiple expressions in if statement you need to use begin. The macro use quasiquote syntax.

You can use macroexpand to see what will be the output of the expression:

(macroexpand (let ((x 0))
               (when (zero? x)
                 (display "zero")
                 (newline))))
;; ==> (let ((x 0))
;; ==>   (if (zero? x)
;; ==>       (begin
;; ==>         (display "zero")
;; ==>         (newline))))

Gensyms

If you create lisp macros you often may end up with expansion and user code to collide and use the same variables. You call this accidental capture of identifiers.

(define-macro (when expr body)
  `(let ((tmp ,expr))
     (if tmp
         (begin
           ,@body))))

If you define macro like this the user of your macro may want to use tmp variable and the code will give unexpected behavior:

(let ((tmp 1000))
  (when (> tmp 0)
    (display tmp)
    (newline)))
;; ==> #t

This will print #t but you expect it to print 1000. This problem can be solved with special kind of symbols called gensyms. Each gensym is a unique symbol.

(define-macro (when expr body)
  (let ((tmp (gensym)))
    `(let ((,tmp ,expr))
       (if ,tmp
           (begin
             ,@body)))))

Notice that let that call gensym is outside quasiquote so it will be evaluated when macro is executed by the output code will have a unique symbol instead of hard coded symbol tmp.

If you try to evaluate the macro, you will get proper results:

(let ((tmp 1000))
  (when (> tmp 0)
    (display tmp)
    (newline)))
;; ==> 1000

Recursive Macros

You can define recursive macros similarly to recursive function. But you need to make sure that the expansion will stop, similarly to recursive functions you may create infinite loops.

(define-macro (alist . body)
  (if (null? body)
      ()
      `(cons (cons ,(car body) ,(cadr body)) (alist ,@(cddr body)))))

You can call this macro to create alist based on its arguments:

(alist "foo" 10 "bar" 20 "baz" 30)
;; ==> (("foo" . 10) ("bar" . 20) ("baz" . 30))

Note recursive call is inside quote and only argument is unquoted. This is required since recursive macro call needs to appear in the expansion. If you call macro recursively and don't return macro call as output list you will end up in ifninite recursive call.

You can see the macro will expand with macroexpand:

(macroexpand (alist "foo" 10 "bar" 20 "baz" 30))
;; ==> (cons (cons "foo" 10) (cons (cons "bar" 20) (cons (cons "baz" 30) ())))

Anaphoric Macros

Anaphoric macros are special kind of macros that leverage the leaking of internal data outside the macro. This is called intentional capture of identifiers. They often expose one or more variable that can be used by the users of the macro.

Example of such macro is aif:

(define-macro (aif test true false)
  `(let ((it ,test))
     (if it
         ,true
         ,false)))

This macro uses it variable to hold the testing value that can be used inside user code:

(let ((alist '((a . 10) (b . 20) (c . 30))))
  (aif (assoc 'a alist)
       (begin
         (display (cdr it))
         (newline))))
;; ==> 10

If you only have one branch like in above code you can define awhen macro:

(define-macro (awhen test . body)
  `(let ((it ,test))
     (if it
         (begin
           ,@body))))

(let ((alist '((a . 10) (b . 20) (c . 30))))
  (awhen (assoc 'a alist)
    (display (cdr it))
    (newline)))
;; ==> 10

Scheme Hygienic Macros

The problem with Lisp macros is that they are not hygienic. But what it means?

Hygiene

If macro is hygienic, it means that it guaranty no leaking of internal code outside of macro. In other words guaranteed not to cause the accidental capture of identifiers. Scheme standard define new macro system called syntax-rules that is hygienic.

But we have gensym is this not enough to make the macros safe? No

Here is an example implementation of unless macro that is part of Scheme that fails because it's not hygienic.

(define-macro (unless test . body)
  `(if (not ,test)
       (begin
         ,@body)))

But in Scheme you can define a variable named not and completely break the macro:

(let ((not (lambda (x) x)))
  (unless #f
    (display "this should not run")
    (newline)))
;; ==> this should not run

This will print the expression because the unless macro uses not procedure that got overwritten by the user code. Hygiene of macros means that something like this can't happen.

Syntax-rules

The syntax-rules in Scheme is different type of macros than lisp macros. It uses a special pattern matching language. Syntax-rules is guarantee by the sec to be hygienic.

Here is the simple definition of a hygienic macro in Scheme:

(define-syntax unless
  (syntax-rules ()
    ((_ test body ...)
     (if (not test)
         (begin
           body ...)))))

This macro is hygienic. If you use same test as before:

(let ((not (lambda (x) x)))
  (unless #t
    (display "this should not run")
    (newline)))

It will not print any value.

Syntax-rules pattern language

Syntax rules macro is defined like this:

(define-syntax foo
  (syntax-rules ()
    ((name <pattern>) <expansion>)
    ((name <different pattern>) <different expansion>)))

The first element if the macro is a list of identifiers that can be used in the pattern.

(define-syntax for
  (syntax-rules (in)
    ((for element in list body ...)
     (for-each (lambda (element)
            body ...)
          list))))

This is an example of a for macro that have in special keyword inside the parentheses. This macro can be used like this:

(for i in '(1 2 3 4)
  (display i)
  (newline))
;; ==> 1
;; ==> 2
;; ==> 3
;; ==> 4

If you try to overwrite the in symbol with variable:

(let ((in #t))
  (for i in '(1 2 3 4)
    (display i)
    (newline)))
;; ==> syntax-rules: no matching syntax in macro

You will get an error because in is no longer a special identifier. It's now a variable.

The rest are the list of pattern and expansion. You can build a shape of the code your macro accept and use part of the pattern in output macro.

the first element of the pattern is often _ it matches against the name of the macro.

Ellipsis

In lisp macros if you wanted to define a list of any values (including no values) you use improper list (list with dot). In syntax-rules pattern you use an ellipsis to indicate a list of items. The ellipsis is after the symbol.

Example of usage of ellipsis:

(define-syntax values
  (syntax-rules ()
    ((_ ((a . b) ...))
     '(a ...))))

This macro use an alist as a pattern and only return the values. Note that it doesn't work on a variable that hold the alist only for alist defined inside the code:

(values ((foo . "lorem") (bar . "ipsum") (baz . "dolor")))
;; ==> (foo bar baz)

Nested Hygienic Macros

There are two ways to defined nested macros, macros that define macros. One is escape of ellipsis with (... ...) syntax.

(define-syntax define-for
  (syntax-rules ()
    ((_ symbol)
     (define-syntax symbol
       (syntax-rules ()
         ((_ (var start end) body (... ...))
          (let loop ((var start))
            (if (<= var end)
                (begin
                  body (... ...)
                  (loop (+ var 1)))))))))))

This macro defines macros that act like for loop, but using tail recursive, named let. You can use this macro like this:

(define-for loop)

(begin
  (loop (i 1 10)
        (display i)
        (if (< i 10)
            (display " ")))
  (newline))
;; ==> 1 2 3 4 5 6 7 8 9 10

Another way to define nested marcros is using SRFI-46 syntax, which allow to change the symbol of ellipsis:

(define-syntax define-for
  (syntax-rules ()
    ((_ symbol)
     (define-syntax symbol
       (syntax-rules ::: ()
         ((_ (var start end) body :::)
          (let loop ((var start))
            (if (<= var end)
                (begin
                  body :::
                  (loop (+ var 1)))))))))))

The macro works exactly the same as previous one:

(define-for loop)

(begin
  (loop (i 1 10)
        (display i)
        (if (< i 10)
            (display " ")))
  (newline))
;; ==> 1 2 3 4 5 6 7 8 9 10

Identifiers

Inside macros you can add identifiers can can be used like keywords from other programming languages. They match only if literal symbol was used and it was not shadowed (overwritten) by variable with same name.

(define-syntax for
  (syntax-rules (==>)
     ((_ (var start ==> end) body ...)
      (let loop ((var start))
         (if (<= var end)
             (begin
                body ...
                (loop (+ var 1))))))))

This for macro define symbol ==> that can be used as part of the syntax:

(let ((start 1)
      (end 10))
  (for (i start ==> end)
     (display i)
     (if (< i end)
         (display " ")))
  (newline))
;; ==> 1 2 3 4 5 6 7 8 9 10

If the symbol ==> is shadowed by local variable the macro will not match and give an error:

(let ((start 1)
      (end 10)
      (==> "this will not work"))
  (for (i start ==> end)
     (display i)
     (if (< i end)
         (display " ")))
  (newline))
;; ==> syntax-rules: no matching syntax in macro

special keywords (created with identifiers) can be optional:

(define-syntax for
  (syntax-rules (==>)
     ((_ (var start end) body ...)
      (_ (var start ==> end) body ...))
     ((_ (var start ==> end) body ...)
      (let loop ((var start))
         (if (<= var end)
             (begin
                body ...
                (loop (+ var 1))))))))

This is recursive syntax-rules that when using without ==> symbol it just add it between start and end.

Recursive Hygienic Macros

You can define recursive hygienic macros, similar to recursive function you need a base case that will stop expansion and recursve case.

(define-syntax alist
  (syntax-rules ()
     ((_) ())
     ((_ x y z ...)
      (cons (cons x y) (alist z ...)))))

Here is example of recursive macro that expand into series of cons. You can use this macro like this:

(alist 'foo 10 'bar 20 'baz 30)
;; ==> ((foo . 10) (bar . 20) (baz . 30))

If the Scheme interpreter of choice support macroexpand on hygienic macros you will see that it expact into series of cons:

(macroexpand '(alist 'foo 10 'bar 20 'baz 30))
;; ==> (#:cons (#:cons (quote foo) 10) (#:cons (#:cons (quote bar) 20) (#:cons (#:cons (quote baz) 30) ())))

The output may be different depending on implementation.

Anaphoric Hygienic Macros

By default Scheme syntax-rules macros don't allow creating anaphoric macros like lisp macro do. But with SRFI-139 you can implement such macros.

Note: that not every scheme implementation support this SRFI.

Here is example of awhen anaphoric macro that use this SRFI:

(define-syntax-parameter it (syntax-rules () ((_) (syntax-error "Use outside aif"))))

(define-syntax awhen
  (syntax-rules ()
    ((_ test body ...)
     (let ((tmp test))
       (syntax-parameterize
        ((it (syntax-rules ()
               ((_) tmp))))
        (if tmp
            (begin
              body ...)))))))

The syntax-paremetirize works similar to parameters from R7RS.

You can use this macro like this:

(let ((alist '((foo . "lorem") (bar . "ipsum") (baz . "dolor"))))
  (awhen (assoc 'bar alist)
    (write (cdr (it)))
    (newline)))
;; ==> "ipsum"

Note the difference, the parameter needs to be wrapped by parentheses like a procedure/macro call.