Macro expansion

45 views
Skip to first unread message

JumpingJupiter

unread,
Oct 10, 2008, 7:03:53 PM10/10/08
to Clojure
Like some other posters to this group, I thought it would be cool to
have Clojure create a GUI based on a declarative definition. My plan
was to use a recursive macro to handle the creation of nested swing
objects and I found myself wanting to view the macro expansions.
Unfortunately macroexpand and macroexpand won't do this for nested
macros:

Note neither macroexpand-1 nor macroexpand expand macros in subforms
(http://clojure.org/macros#toc12)

For example:

user=> (macroexpand
'(cond
(and (= a b) (= c d)) true
(or (= e f) (= g h)) false))

-> (if (and (= a b) (= c d)) true (cond (or (= e f) (= g h)) false))

The initial 'cond' has been expanded into an 'if', but cond is a
recursive macro and the expansion produces another 'cond'. Furthermore
the 'and' & 'or' macros are unexpanded.

So, I wrote a function to produce a full macro expansion. I guess this
has been done a million times for other lisps, but since I'm a lisp
novice I had some fun doing it myself. After several ugly attempts
(which were educational in their own right) I came up with the
following set of functions.

(defn mapr
"recursively maps a function to a sequence and
subsequences within the map results."
[f & sequence]
(defn maprec [form]
(if (seq? form)
(map maprec (f form))
form))
(first (maprec sequence)))

mapr maps a supplied function over a sequence and then maps the same
function over nested subsequences in the resulting collection. The
inner function maprec is interesting because it recurses by passing
itself to map. This is probably heavy on the stack, but that shouldn't
be a problem for my purposes.

Now, with mapr, it's trivial to expand all the macros within a form.
Simply call mapr passing the macroexpand function and the form to be
expanded.

(defn macroexpand-r
"Expands all macros in a form and its subforms."
[forms]
(mapr macroexpand forms))

Repeating the above example with macroexpand-r:

user=> (macroexpand-r
'(cond
(and (= a b) (= c d)) true
(or (= e f) (= g h)) false))

-> (let* [and__196 (= a b)] (if and__196 (= c d) and__196)) true
(if (let* [or__202 (= e f)] (if or__202 or__202 (= g h)))
false nil))

All the 'cond's 'and's & 'or's have been replaced with the 'if' and
'let' special forms. Pretty ugly isn't it?

That's useful, but too much since I will usually only be interested in
a single macro. I will want to expand instances of that macro and
leave others unexpanded. I can do that by passing mapr a function
which conditionally expands macros:

user=> (mapr (fn [f] (if (= 'cond (first f)) (macroexpand f) f))
'(cond
(and (= a b) (= c d)) true
(or (= e f) (= g h)) false))

-> (if (and (= a b) (= c d)) true (if (or (= e f) (= g h)) false
nil))

Now the cond has been expanded (twice) but the 'and' & 'or' remain.
Just what I want, except I can't be bothered writing the fn everytime
so:

(defn macroexpand-only
"Expands all macros in a form and its subforms that match a given
symbol."
[macro forms]
(mapr
(fn [f] (if (= macro (first f)) (macroexpand f) f))
forms))


user=> (macroexpand-only 'and '(cond (and (= a b) (= c d)) true (or (=
e f) (= g h)) false))

-> (cond (and (= a b) (= c d)) true (let* [or__202 (= e f)] (if
or__202 or__202 (clojure/or (= g h)))) false)


user=> (macroexpand-only 'or '(cond (and (= a b) (= c d)) true (or (=
e f) (= g h)) false))

-> (cond (let* [and__196 (= a b)] (if and__196 (clojure/and (= c d))
and__196)) true (or (= e f) (= g h)) false)


user=> (macroexpand-only 'cond '(cond (and (= a b) (= c d)) true (or
(= e f) (= g h)) false))

-> (if (and (= a b) (= c d)) true (if (or (= e f) (= g h)) false nil))

Oops, looks like I've a got a problem. macroexpand sometimes produces
qualified names in the expansions and sometimes doesn't:

user=> (macroexpand '(cond (= a b) true (= c d) false))

-> (if (= a b) true (cond (= c d) false))

user=> (macroexpand '(or (= a b) (= c d)))

-> (let* [or__202 (= a b)] (if or__202 or__202 (clojure/or (= c d))))

There is an unadulterated recursive cond in the expansion of the cond
form. But the expansion of the 'or' form contains the 'closure/or'
symbol. This is because the definition of cond quotes its recursive
call while the 'or' (and 'and') definitions don't. Is this a bug? or
if not can someone explain the different usage? And while I'm asking
questions, why don't macroexpand and macroexpand-1 expand macros in
subforms?

The macroexpand-only function could be modified to accommodate the
different representations of macros, but I'll leave that for another
day.

And that's it.

If you're going to use these macros, be warned, they're only minimally
tested and I don't know what I'm doing. I wouldn't be at all surprised
if there's a good reason why Rich hasn't made something like this part
of Clojure. (or maybe he has and I just didn't notice)


Now, where was I ...


Reply all
Reply to author
Forward
0 new messages