Common Lisp vs. Scheme macros

September 16th, 2007 at 7:59 pm

I’m in the process of trying to decide which language is better for me – Common Lisp and Scheme. One of the crucial points to take into account is macros, since I personally consider macros to be the most salient feature of Lisp which sets it apart from other languages.

The choice is far from simple, of course. On one hand, CL’s macros are conceptually much simpler than Scheme’s. While defmacro is pure CL code with the difference that it gets executed at compile-time, Scheme’s syntax-rules is a specialized sub-language which is almost-but-not-quite Scheme. Therefore, it loses the convenient equivalence to normal Scheme code and is, at least for me, much harder to understand and much less intuitive to use.

On the other hand, CL’s defmacro suffers from the lack of macro hygiene, as opposed to Scheme’s macro system.

Simple examples of this symptom are easy to fix. Consider this macro (from Practical Common Lisp) – iterating over prime numbers in a given range:

(defmacro do-primes ((var start end) &body body)
  `(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
        (ending-value ,end))
       ((> ,var ending-value))
     ,@body))

The problem here is with ending-value. This code is broken:

(do-primes (ending-value 0 10)
  (print ending-value))

Because ending-value is being re-binded inside do-primes. Fortunately, this case is easy to fix:

(defmacro do-primes ((var start end) &body body)
  (let ((ending-value-name (gensym)))
    `(do ((,var (next-prime ,start) (next-prime (1+ ,var)))
          (,ending-value-name ,end))
         ((> ,var ,ending-value-name))
       ,@body)))

However, there are cases that can’t be fixed in any simple way. For example, look at the last code sample again – what if do-primes is called inside some context where do is redefined to be a different function (with flet or labels). Sure, this is a far fetched example – but it can happen, and the code will surely break. Think about it – the macro can be used successfully for years, until it gets use in a slightly weird piece of code – and then it will fail in a mysterious way which is quite difficult to debug.

Another far-fetched-but-can-happen problem: if a macro refers to some global symbol and is called inside a lexical context that shadows this symbol. Another hard-to-fix problem.

There are probably other examples of where defmacro can break. Sure, with a judicious use of gensyms and solid coding conventions the probability of something happening is low, and indeed, tons of CL code with macros work perfectly. However, there’s this nagging feeling of a looming disaster that may strike at some unknown time with a slightly non-standard piece of code. This isn’t pleasant.

Scheme’s macro system is hygienic and doesn’t suffer from these problems. Of course, this comes at the price of simplicity and ease-of-use, as I mentioned earlier.

P.S. Interestingly, you can write code with defmacro in Scheme if you want. Most of the major implementations support it. It appears that Scheme’s macros can be used to implement (non-hygienic) CL macros, but not the other way around.

Related posts:

  1. Rant about Common Lisp and implementations
  2. Giving PLT Scheme a try
  3. The parentheses of Lisp
  4. Book review: “Successful Lisp” by David Lamkins
  5. back to lisp

5 Responses to “Common Lisp vs. Scheme macros”

  1. Barry PerrymanNo Gravatar Says:

    Paul Grahams excellent On Lisp book, which you can get in PDF form from his web site, has a good chunk of the book devoted to the darker corners of macros.

    One of the type of macros he develops is an Anaphoric macro for things like IF. So, from memory, the CL code would be something like

    (defmacro aif (test true-branch &optional false-branch)
    `(let ((it ,test))
    (if it
    ,true-branch
    ,false-branch)))

    Which enables you to write code like this:

    (aif (position #\: my-string)
    (subseq my-string 0 it)
    “”)

    where ‘it’ refers to the value that the test returned. This is a macro for the following type of pattern, shown here in the following similar code:

    (let ((pos (position #\” my-string)))
    (if pos
    (subseq my-string 0 pos)
    “”))

    AFAIK this isn’t possible with Hygenic macros, because they see the ‘it’ defined in the ‘let’ expression as something that needs to be hygenic, and thus you can’t refer to it in your usage of aif.

  2. Andrew GwozdziewyczNo Gravatar Says:

    Scheme implementations commonly support other types of macros that allow you to break hygiene for a specific instance. syntax-case macros are hygienic and use the syntax-rules language but they introduce a few forms such as datum->syntax and syntax->datum which can be used to break hygiene. Explicit renaming macros, found in scheme48, allow you to write defmacro style macros while rebinding the forms you use to new names to keep things hygienic. MIT scheme supports syntaxtic closures which allows a macro to use the bindings present at the time of the macros definition, because it creates a closure with them enclosed. These are also defmacro like. However, it is of course the case that different schemes implement different systems, but I dont’ think it’s a case where a system cannot be ported to another, it just adds a bunch of extra work. Good luck with choosing!

  3. Jens Axel S√łgaardNo Gravatar Says:

    Hi Eli.

    Here is the macro in the syntax-case system.

    (require (planet “math.ss” (“soegaard” “math.plt” 1 2)))

    (define-syntax (do-primes stx)
    (syntax-case stx ()
    [(do-primes var start end body ...)
    #'(let ([ending-value end])
    (do ((var (next-prime start) (next-prime (+ 1 var))))
    [(> var ending-value)]
    body …))]))

    Example:

    > (do-primes p 2 17
    (display p)
    (newline))
    3
    5
    7
    11
    13
    17

  4. John ConnorsNo Gravatar Says:

    You might be interested in this: Scheme macros for Common Lisp.

    http://www.ccs.neu.edu/home/dorai/mbe/mbe-lsp.html

  5. mtlNo Gravatar Says:

    You’ll find that if you try to redefine DO, you’ll get an error.

    As for global names, good convention is to surround dynamic names with asterisks, so as to avoid any conflict. Furthermore, it’s not good programming practice to use such symbols in a macro.

    So no, it’s not hard to fix.

    You’re nitpicking quite a lot here. Redefining DO in an flet is not “slightly non-standard,” it’s absolutely crazy, even if it were possible.