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.
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:
One way to learn Scheme macros is simply by writing as many as possible, as is done in the remainder of this chapter.
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.
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.