The syntax-rule macro system of R5RS does not seem to scale beyond simple macros. It is very difficult to write macros that compose, to assemble complex macros from already written and tested components. The previous approaches to composable syntax-rules are heavy, notationally and computationally. This article presents an alternative, lightweight style of writing composable syntax-rules, based on the CK abstract machine.

We demonstrate recursive, higher-order applicative macros defined in the style that looks like that of ML or (strict) Haskell. We write composable, call-by-value--like macros without resorting to the continuation-passing-style and thus requiring no macro-level lambda. The syntax remains direct-style, with nested applications.

Syntax-rules are difficult to compose because of their evaluation order: the arguments of a macro-invocation are not expanded. That per se does not preclude functional composition since the normal-order lambda-calculus or non-strict languages like Haskell do not evaluate arguments of a function application either. However, lambda-calculus has first-class anonymous abstractions; Haskell also has the case form that forces evaluation of an expression, to the extent needed to choose among the pattern-match clauses. Syntax-rules have none of these compensating features. Generally, a syntax-rule cannot obtain the result of the expansion of its argument expression. The article on Systematic Macro Programming on this page explains the composability problem in detail. So far, the only way out has been to effectively change the evaluation order by writing macros in the continuation-passing style (CPS). However, CPS code is hard to read. Furthermore, building continuations requires the notation for first-class, preferably anonymous syntax-rule abstractions. Although the latter are possible to emulate, the result is stylistically ugly and computationally expensive. Some macro expanders take the shocking amount of time and memory to expand CPS macros with anonymous abstractions.

This project was inspired by the question posed by Dan Friedman in March 2009:

Write the macro permute that takes any number of arguments and returns the list of their permutations:

(permute a b c) ==> ((a b c) (b a c) (b c a) (a c b) (c a b) (c b a))

The order of the entries in the list is immaterial. One should write permute without resorting to CPS.

perm :: [a] -> [[a]] perm [] = [[]] perm (h:t) = concatMap (ins h) (perm t) ins :: a -> [a] -> [[a]] ins x [] = [[x]] ins x (h:t) = (x:h:t) : map (h:) (ins x t)

Our macros are written in a CK style. We distinguish values, which are always quoted like '(1 2 3) , from general applicative expressions such as (c-append '(1) (c-cons '2 '(3 4))) , which may contain nested applications. Values are regarded as results of the CK evaluation. Here is the first example, of the CK-style macro cons :

(define-syntax c-cons (syntax-rules (quote) ((c-cons s 'h 't) (ck s '(h . t)))))

s

s

ck

s

ck

s

c-cons

We now demonstrate recursion and functional composition, or nested application. We define a macro c-append , using the just defined c-cons .

(define-syntax c-append (syntax-rules (quote) ((c-append s '() 'l2) (ck s 'l2)) ((c-append s '(h . t) 'l2) (ck s (c-cons 'h (c-append 't 'l2)))) ))

append [] l2 = l2 append (h:t) l2 = h : (append t l2)

At the heart of the CK style is the CK abstract machine, which focuses and re-focuses an applicative expression, relying on user-defined CK-macros for (primitive) reductions. The machine is implemented as a syntax-rule ck . It operates on the stack (the first argument to all CK macros) built out of the frames (op va ... [] ea ...) , represented as ((op va ...) ea ...) in the code below. Here op is the name of a CK-macro to do the reduction; zero or more va must all be values; zero or more ea are arbitrary expressions.

(define-syntax ck (syntax-rules (quote) ((ck () 'v) v) ; yield the value on empty stack ((ck (((op ...) ea ...) . s) 'v) ; re-focus on the other argument, ea (ck s "arg" (op ... 'v) ea ...)) ((ck s "arg" (op va ...)) ; all arguments are evaluated, (op s va ...)) ; do the redex ((ck s "arg" (op ...) 'v ea1 ...) ; optimization when the first ea (ck s "arg" (op ... 'v) ea1 ...)) ; was already a value ((ck s "arg" (op ...) ea ea1 ...) ; focus on ea, to evaluate it (ck (((op ...) ea1 ...) . s) ea)) ((ck s (op ea ...)) ; Focus: handle an application; (ck s "arg" (op) ea ...)) ; check if args are values ))

We get the ball rolling by expanding (ck () exp) to evaluate the CK-expression exp on the empty initial stack. The Scheme expression (ck () (c-append '(1 2 3) '(4 5))) will hopefully macro-expands to (1 2 3 4 5) , which the Scheme expression evaluator will try to evaluate, reporting the error since 1 is not a procedure. To see the result of just the macro-expansion, without any further evaluations, we should quote it:

(define-syntax c-quote (syntax-rules (quote) ((c-quote s 'x) (ck s ''x))))

(ck () (c-quote (c-append '(1 2 3) '(4 5)))) ; ==> (1 2 3 4 5)

A higher-order map macro takes as the first, non- s , argument a closure to apply to each element of the list given in the second argument.

(define-syntax c-map (syntax-rules (quote) ((c-map s 'f '()) (ck s '())) ((c-map s '(f ...) '(h . t)) (ck s (c-cons (f ... 'h) (c-map '(f ...) 't)))) ))

(ck () (c-quote (c-map '(c-cons '10) '((1) (2) (3) (4))))) ; ==> ((10 1) (10 2) (10 3) (10 4))

concatMap

(define-syntax c-concatMap (syntax-rules (quote) ((c-concatMap s 'f '()) (ck s '())) ((c-concatMap s '(f ...) '(h . t)) (ck s (c-append (f ... 'h) (c-concatMap '(f ...) 't)))) )) (ck () (c-quote (c-concatMap '(c-cons '10) '((1) (2) (3) (4))))) ; ==> (10 1 10 2 10 3 10 4)

We have defined enough of the syntax-rule list processing library to solve Dan Friedman's problem:

(define-syntax c-perm (syntax-rules (quote) ((c-perm s '()) (ck s '(()))) ((c-perm s '(h . t)) (ck s (c-concatMap '(c-ins 'h) (c-perm 't)))))) (define-syntax c-ins (syntax-rules (quote) ((c-ins s 'x '()) (ck s '((x)))) ((c-ins s 'x '(h . t)) (ck s (c-cons '(x h . t) (c-map '(c-cons 'h) (c-ins 'x 't))))))) ; The following macro is a syntactic sugar to invoke c-perm (define-syntax perm (syntax-rules () ((perm . args) (ck () (c-quote (c-perm 'args))))))

(perm 1 2 3) ; ==> ((1 2 3) (2 1 3) (2 3 1) (1 3 2) (3 1 2) (3 2 1))