how does Clojure macros work?

153 views
Skip to first unread message

Michele Simionato

unread,
Jun 26, 2009, 3:53:16 AM6/26/09
to Clojure
I am writing a rather long series of articles about Scheme on Artima,
"The Adventures of a Pythonista in Schemeland" (maybe somebody here
has
heard of it). Last week I arrived at point of discussing hygienic
macros
(http://www.artima.com/weblogs/viewpost.jsp?thread=260195)
and I wanted to spent a few words about the differences between
Scheme and other lisps. In particular I want to asset the status of
Clojure
macros with respect to hygiene.

Since they look a lot like Common Lisp macros, I initially assumed
that
Clojure macros were not hygienic, but after performing some experiment
I
realized that I was half wrong. For instance, I expected this code to
fail due to free-symbol capture:

user=> (defmacro m [x] `(list ~x))
#'user/m
user=> (let [list 1] (m 2))
(2)

Instead, it works correctly. Therefore, I assume that Clojure
is renaming the free symbols in the macro body (in this case 'list).
On the other hand, Clojure macros are not fully hygienic, otherwise
a would not be defined in this example:

user=> (defmacro def-a[x] `(def a ~x))
#'user/def-a
user=> (def-a 1)
#'user/a
user=> a
1

It seems that in order to avoid variable capture for bound symbols
I need to use good old (gensym):

user=> (defmacro def-a[x] (let [a (gensym)] `(def ~a ~x)))
#'user/def-a
user=> (def-a 1)
#'user/G__77

Am I right in my assumptions? Is there a document explaining how
Clojure macros work? The macro system seems quite unique, in the sense
that I do not know of any other macro system working in the same way.

Michele Simionato

Michele Simionato

unread,
Jun 26, 2009, 9:22:27 AM6/26/09
to Clojure
On Jun 26, 9:53 am, Michele Simionato <michele.simion...@gmail.com>
wrote:
> I want to asset the status of Clojure
> macros with respect to hygiene.

Some further experiment:

$ clj
Clojure 1.1.0-alpha-SNAPSHOT
user=> (def x 42)
#'user/x
user=> (defmacro m[] 'x)
#'user/m
user=> (m)
42
user=> (let [x 43] (m))
43

There is no referential transparency, it seems. Also, it looks like
Clojure macros are runtime macros,
i.e. they may depend from runtime values and macro expansion cannot be
performed statically
without running the program (which is usually considered pretty bad).
Am I correct?
Finally, as an unrelated question, is there an equivalent of macrolet
in Clojure?
Thanks,

Michele Simionato

Rich Hickey

unread,
Jun 26, 2009, 9:42:53 AM6/26/09
to clo...@googlegroups.com

Not quite. But it takes a bit more digging to see how. Most important,
the macro does not yield an expansion with an unqualified 'a:

user=> (ns foo)
nil

foo=> (defmacro def-a[x] `(def a ~x))
#'foo/def-a

;use macroexpand to see what the macro does
foo=> (macroexpand-1 '(def-a 42))
(def foo/a 42)

Therefor, its use is still hygienic - in the expansion, 'a means
whatever it means in ns foo:

foo=> (in-ns 'user)
#<Namespace user>

;currently defining vars can't be done from a foreign namespace, so
this fails as expected

user=> (foo/def-a 42)
java.lang.Exception: Can't refer to qualified var that doesn't exist
(NO_SOURCE_FILE:9)

user=> (in-ns 'foo)
#<Namespace foo>

foo=> a
java.lang.Exception: Unable to resolve symbol: a in this context
(NO_SOURCE_FILE:0)

foo=> (def-a 42)
#'foo/a

foo=> a
42

> Is there a document explaining how
> Clojure macros work? The macro system seems quite unique, in the sense
> that I do not know of any other macro system working in the same way.
>

It is unique, and it is not renaming. I call the process resolving,
and the basic purpose is hygiene - macros yield qualified symbols
reflecting their meanings at macro definition time.

http://clojure.org/reader#syntax-quote
http://clojure.org/evaluation

You can use auto-gensyms (bar#) to get unique names, but you must take
extra effort to get a macro to emit an unqualified (and thus
capturing) symbol, e.g. ~'bar will yield the unqualified symbol 'bar
in the expansion.

Hope that helps (tip - use macroexpand while exploring macros)

Rich

Rich Hickey

unread,
Jun 26, 2009, 9:51:58 AM6/26/09
to clo...@googlegroups.com
On Fri, Jun 26, 2009 at 9:22 AM, Michele
Simionato<michele....@gmail.com> wrote:
>
> On Jun 26, 9:53 am, Michele Simionato <michele.simion...@gmail.com>
> wrote:
>> I want to asset the status of Clojure
>> macros with respect to hygiene.
>
> Some further experiment:
>
> $ clj
> Clojure 1.1.0-alpha-SNAPSHOT
> user=> (def x 42)
> #'user/x
> user=> (defmacro m[] 'x)
> #'user/m
> user=> (m)
> 42
> user=> (let [x 43] (m))
> 43
>
> There is no referential transparency, it seems.

How not?

> Also, it looks like
> Clojure macros are runtime macros,
> i.e. they may depend from runtime values and macro expansion cannot be
> performed statically
> without running the program (which is usually considered pretty bad).
> Am I correct?

No. Please use macroexpand in order to facilitate yourunderstanding.
By using quote, and not syntax-quote, you have written an
intentionally capturing macro:

user=> (def x 42)
#'user/x
user=> (defmacro m[] 'x)
#'user/m

user=> (macroexpand '(m))
x
user=> (let [x 43] (m)) ;i.e. (let [x 43] x)
43

The correct way is to use syntax-quote:

user=> (defmacro m[] `x)
#'user/m

user=> (macroexpand '(m))
user/x
user=> (let [x 43] (m)) ;i.e. (let [x 43] user/x)
42

> Finally, as an unrelated question, is there an equivalent of macrolet
> in Clojure?

Not yet.

Rich

Michele Simionato

unread,
Jun 26, 2009, 10:39:22 AM6/26/09
to Clojure

On Jun 26, 3:51 pm, Rich Hickey <richhic...@gmail.com> wrote:
> By using quote, and not syntax-quote, you have written an
> intentionally capturing macro

Acc, I missed that. I have read the documentation of syntax-quote now:

""
For Symbols, syntax-quote resolves the symbol in the current context,
yielding a fully-qualified symbol (i.e. namespace/name or
fully.qualified.Classname). If a symbol is non-namespace-qualified and
ends with '#', it is resolved to a generated symbol with the same name
to which '_' and a unique id have been appended. e.g. x# will resolve
to x_123. All references to that symbol within a syntax-quoted
expression resolve to the same generated symbol.
""

That means that I do not need gensym, I can just add a `#` to the
identifiers I want to introduce hygienically, right?
I guess this is what you meant by autogensym in the other post. A
pretty cool idea, actually.

Laurent PETIT

unread,
Jun 26, 2009, 11:10:08 AM6/26/09
to clo...@googlegroups.com
Hi,

2009/6/26 Michele Simionato <michele....@gmail.com>

The unique gotcha I'm aware of with the '#' autogensym is that it is transformed at read time, so be aware of not using it in some loop assuming that you will have a new identifier each time, or do not use it to generate an identifier that would be defined (by using a variant of def -> defn, defstruct, defmacro ..) by the macro (if you do so, then each new macro invocation would continuously override a unique definition, rather than creating new ones with unique names).



Regards,

--
Laurent


Meikel Brandmeyer

unread,
Jun 26, 2009, 11:14:36 AM6/26/09
to clo...@googlegroups.com
Hi,

Am 26.06.2009 um 16:39 schrieb Michele Simionato:

> That means that I do not need gensym, I can just add a `#` to the
> identifiers I want to introduce hygienically, right?
> I guess this is what you meant by autogensym in the other post. A
> pretty cool idea, actually.

Exactly, here a short real-world example.

(defmacro with-out-str
[& body]
`(let [out-string# (StringWriter.)]
(binding [*out* out-string#]
~@body)
(.toString out-string#)))

This macro is 100% hygienic. Things like
binding or StringWriter are resolved to the
binding and StringWriter in effect, when the
macro was defined. Also out-string# generates
a new local which does not clash with the
user code injected via ~@body.

I think this is a very easy way to write macros.
It is almost a visual template of the final code.

Note: I didn't investigate whether there is
a StringWriter and what it's interface is. The
point of the example is the macro...

Sincerely
Meikel

Konrad Hinsen

unread,
Jun 26, 2009, 9:35:06 PM6/26/09
to clo...@googlegroups.com
On 26.06.2009, at 15:22, Michele Simionato wrote:

> Finally, as an unrelated question, is there an equivalent of macrolet
> in Clojure?

Not in Clojure itself, but there is an implementation in an external
library, clojure.contrib.macro-utils:
http://github.com/richhickey/clojure-contrib/blob/
82cf0409d0fcb71be477ebfc4da18ee2128a2ad1/src/clojure/contrib/
macro_utils.clj

Concerning your other questions, I think most of them have been
answered already. In general, the main difference between macros in
Clojure and Common Lisp comes from the fact that Clojure has
namespaces and both namespace-qualified and unqualified symbols. This
takes care of many of the typical problems with non-hygienic macros
in Common Lisp, while still permitting intentional variable capture.
It's a pretty neat design once you have understood all the details!

Konrad.

Reply all
Reply to author
Forward
0 new messages