Clojure provides a set of reader macros which make life a little easier. They are basically short-hand notations for often used functions/macros. Here is a table:
Reader Macro | Expansion | |
'x | (quote x) | |
@x | (deref x) | |
^x | (meta x) | |
#(x) | (fn [] (x)) |
The astute reader might already have noticed, that #^
is missing from the
list. Isn't #^y x
equivalent to (with-meta x y)
?
How does a reader macro actually work? As the name says the magic happens
inside the reader. When it hits a certain character – eg. '
, ^
, @
, "
or #
– it switches to some different mode. "
for example starts parsing a
String from the input stream. Some of these special characters don't influence
the way the stream is actually read, but wrap the next read thing into function
or macro. Most examples from the above table or of this category.
While this latter category of reader macros mostly provides some sugar for often used functions or macros, one has to take care to understand the implications. There were some interesting examples on the Clojure group recently. Take for example the following two expressions:
^#^{:meta :data} 'foo
^'#^{:meta :data} foo
What is the difference? The first line returns nil
while the second returns
{:meta :data}
. Why is that so? Let's have a look at a partial expansion of
the expressions.
(meta #^{:meta :data} (quote foo))
(meta (quote #^{:meta data} foo))
Here we see, that in the first expression the metadata is actually put on the
list (quote foo)
which is „the next thing read“. Since meta
is a
function, the expression gets evaluated to the Symbol foo
, which does not
have any metadata associated. So meta
returns nil
.
In the second expression the metadata is attached to foo
which gets quoted by
quote
. But now meta
can see the metadata and hence returns it.
For completeness sake: #^Classname
is equivalent to #^{:tag Classname}
.
I didn't expand #^
with with-meta
in the above example. Why? Because here
we have another subtle issue: they are simply not equivalent. Let's have a
closer look with an example.
#^{:tag String} s
(with-meta s {:tag String})
So what happens in the first case? The reader hits #^
and recognises, that he
should put the map following the #^
onto the next thing read as metadata. He
reads the Symbol s
and associates the map as metadata with the symbol.
Nothing surprising. BUT: this happens in the reader! There is no evaluation
whatsoever. In particular the String
in the map is a Symbol and not the Java
class „String“.
In the with-meta
case, things are different. with-meta
is a function it is
called during runtime. s
will be evaluated to the value it names. So
depending on the situation it might be a Vector, nil
or unbound. Furthermore
the map argument gets also evaluated and the String
in the map with the Java
class „String“1.
So there are fundamentally different things going on here.
Be careful with reader macros. „Understanding how things work“ is a basic requirement every serious programmer has to master. But with reader macros things get even more complicated due to the subtle interactions between the different processing stages when compiling the Clojure code.
#^
is now deprecated. It is replaced by ^
and the previous functionality of ^
is gone. So no short-hand anymore for meta
.
A short sidenote for macro writers: if you want to type-hint something in your macro, you have to quote the classname!
(defmacro my-string-defn
[fn-name args & body]
`(defn ~(with-meta fn-name {:tag `String}) ~args ~@body))
Excercises:
with-meta
here?String
quoted with `` and not
'`?Published by Meikel Brandmeyer on .
I'm a long-time Clojure user and the developer of several open source projects mostly involving Clojure. I try to actively contribute to the Clojure community.
My most active projects are at the moment VimClojure, Clojuresque and ClojureCheck.
Copyright © 2009-2014 All Right Reserved. Meikel Brandmeyer