Prev Index Next

Macros (Basic)

So, I talked about how expressions are evaluated in normal or applicative order. Scheme lambda expressions evaluate in applicative order. Procedure calls also evaluate in applicative order. What confused me was, what about normal order?

It turns out, there are some things, like conditionals, which you cannot write with applicative order lambda expressions. One example are if expressions. It makes sense, then, to learn conditionals next, however, as I did so, I found out that they are built upon macros. So to learn conditionals, I had to know a thing or two about macros.

What are macros?

Macros are programs that write programs, or as I prefer it, macro expressions expand into expressions. The latter closer describes what really happens, as macros are expanded in a separate evaluation phase, before code interpretation or compilation.

At its best, programming in Scheme is an iterative process of building up a language appropriate to the problem at hand, and then solving the problem in that language. Defining new procedures is part of that, but Scheme also allows the user to extend its syntax, with its famous “macros”. Guile Manual.

Macros simplify and abstract away code. They are commonly used; like cond, a macro for nested if expressions, which you will learn in the next chapter. Macros resemble procedures and are often mistaken for them. The differences are:

One way to know whether you have a procedure or a macro is to simply write the identifier as an expression and check if it self-evaluates:

> +
$1 = #<procedure + (#:optional _ _ . _)>
> cond
While compiling expression:
Syntax error:
unknown location: source expression failed to match any pattern in form cond

Let us talk about macro syntax. Scheme syntax for macro definitions is complex. In Guile, you can define macros in three ways: syntax-case, syntax-rules, and define-macro. Luckily, both syntax-rules and define-macro simply use syntax-case in the background, which means you only have to learn syntax-case, as it is the most powerful. The Guile Reference Manual discusses the other two. Other Scheme implementations, like GNU/Mit Scheme do not support syntax-case macros, but do support the less powerful syntax-rules. I prefer to use implementations that support syntax-case.

Consider once more that macros expand expressions into expressions. There is an expression before the marco expansion and an expression after. So, you can write macros simply in three steps:

  1. write the expression before expansion (EBE);
  2. write the expression after expansion (EAE);
  3. iteratively write the macro definition.

One way to learn Scheme macros is simply by writing as many as possible, as is done in the remainder of this chapter.

The identity macro

One of the simplest macros is the identity, where the EBE:

(identity (lambda () 1))

is the same as the EAE:

(lambda () 1)

Let us start writing the macro with:

(define-syntax identity (lambda (x) (syntax-case x ()

The macro body that describes the EBE is:

(define-syntax identity (lambda (x) (syntax-case x ()
  ((identity arg)

The macro body that describes the EAE is:

(define-syntax identity (lambda (x) (syntax-case x ()
  ((identity arg)
   (syntax arg)))))

In Guile, you can expand a macro definition by typing the ,expand interpreter command before the expression.

> ,expand (identity 1)
$1 = 1
> ,expand (identity (+ 1 2))
$2 = (+ 1 2)
> ,expand (identity (lambda (x) x))
$3 = (lambda (x) x)

Observe the fundamental difference between procedures and a macro. In a macro expansion, the operands are not evaluated.

Suppose the identity macro accepts any number of arguments. Here is the EBE:

(identity (lambda (x) x) (lambda (x y) x))

Here is the EAE:

(lambda (x) x) (lambda (x y) x)

Now finish the macro. Take the previous macro definition. Add a new syntax-pattern, which describes the EBE:

(define-syntax identity (lambda (x) (syntax-case x ()
  ((identity arg)
   (syntax arg))
  ((identity arg ...)

Now write the EAE. Here I use the Scheme’s begin macro which allows you to sequence multiple expressions, such that the result is determined by the final expression in the sequence.

(define-syntax identity (lambda (x) (syntax-case x ()
  ((identity arg)
   (syntax arg))
  ((identity arg ...)
   (syntax (begin arg ...))))))

Let us expand this macro.

> ,expand (identity (lambda (x) x) (lambda (x y) x))
$4 = (begin (lambda (x) x) (lambda (x y) x))

Guile defines a special #' prefix that can be used to replace syntax. The following identity macro definition is identical to the previous one:

(define-syntax identity (lambda (x) (syntax-case x ()
  ((identity arg)
   #'arg)
  ((identity arg ...)
   #'(begin arg ...)))))

The prefix #' is syntactic sugar and it is called sharp-quote.

Compared to C

Sometimes one is stumped by the difference in C macros and Lisp or Guile macros. What do these languages do differently? While in the former, macros are simply text substitutions, in the latter, macros are a part of the language itself.

Let us write an example to illustrate this.

It is often that in C you define constants using macros. Sometimes, these constants use C functions, for example: #define my-constant (1/sqrtf(2)). The C compiler then simply replaces each occurrance of my-constant with (1/sqrtf(2)).

But in Lisp or Guile, macros are more powerful. Let us see how.

The EBE is:

(my-constant)

The EAE is:

0.707106...

In Guile, you can simply compute (/ 1 (sqrt 2)) within the macro.

Write the macro body for the EBE.

(define-syntax my-constant (lambda (x) (syntax-case x ()
  ((my-constant)

Write the macro body for the EAE.

(define-syntax my-constant (lambda (x) (syntax-case x ()
  ((my-constant)
    (/ 1 (sqrt 2))))))

Observe the macro expansion:

> ,expand (my-constant)
$1 = 0.7071067811865475

Compare this to the C-like behaviour:

(define-syntax my-constant-c-like (lambda (x) (syntax-case x ()
  ((my-constant)
   #'(/ 1 (sqrt 2))))))

> ,expand (my-constant-c-like)
$2 = (/ 1 (sqrt 2))

Now simply use it:

> (my-constant)
$3 = 0.7071067811865475
> (+ 1 (my-constant))
$4 = 1.7071607811865475

This simple example illustrates the power of Lisp or Guile macros.

Prev Index Next