Last modified on 18 February 2013, at 02:47

Learning Clojure/Reader Macros

« Learning Clojure
Reader Macros
»
Leiningen Unit Testing

Reader MacrosEdit

A reader macro (not to be confused with a regular macro) is a special character sequence which, when encountered by the reader, modifies the reader behavior. Reader macros exist for syntactical concision and convenience.

'foo                  ; (quote foo)
#'foo                 ; (var foo)
@foo                  ; (clojure.core/deref foo)
#^{:ack bar} foo      ; (clojure.core/with-meta foo {:ack bar})
 ^{:ack bar} foo      ; (clojure.core/with-meta foo {:ack bar})
#"regex pattern"      ; create a java.util.regex.Pattern from the string (this is done at read time, 
                      ; so the evaluator is handed a Pattern, not a form that evaluates into a Pattern)
#(foo %2 bar %)       ; (fn [a b] (foo b bar a))

The #() syntax is intended for very short functions being passed as arguments. It takes parameters named %, %2, %3, %n ... %&.

The most complicated reader macro is syntax-quote, denoted by ` (back-tick). When used on a symbol, syntax-quote is like quote but the symbol is resolved to its fully-qualified name:

`meow    ; (quote cat/meow) ...assuming we are in the namespace cat

Applying syntax-quote to an atomic value expands to that same value. For instance:

`10      ; expands to 10
`1/2     ; expands to 1/2
`"hello" ; expands to "hello"

When used on a list, vector, or map form, syntax-quote quotes the whole form except, a) all symbols are resolved to their fully-qualified names and, b) components preceded by ~ are unquoted:

(defn rabbit [] 3)
`(moose ~(rabbit))     ; (quote (cat/moose 3))   ...assume namespace cat
(def zebra [1 2 3])
`(moose ~zebra)        ; (quote (cat/moose [1 2 3]))

Components preceded by ~@ are unquote-spliced:

`(moose ~@zebra)       ; (quote (cat/moose 1 2 3))

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.

`(x#)                  ; (x__2804__auto__)

For all forms other than Symbols, Lists, Vectors and Maps, `x is the same as 'x.

Syntax-quotes can be nested within other syntax-quotes:

`(moose ~(squirrel `(whale ~zebra)))   

For Lists syntax-quote establishes a template of the corresponding data structure. Within the template, unqualified forms behave as if recursively syntax-quoted.

`(x1 x2 x3 ... xn)

is interpreted to mean

(clojure.core/seq (clojure.core/concat |x1| |x2| |x3| ... |xn|))

where the | | are used to indicate a transformation of an xj as follows:

  • |form| is interpreted as (clojure.core/list `form), which contains a syntax-quoted form that must then be further interpreted.
  • |~form| is interpreted as (clojure.core/list form).
  • |~@form| is interpreted as form.

If the syntax-quote syntax is nested, the innermost syntax-quoted form is expanded first. This means that if several ~ occur in a row, the leftmost one belongs to the innermost syntax-quote.

An important exception is the empty list:

`() 

is interpreted to mean

(clojure.core/list)

Following the rules above, and assuming that the var a contains 5, an expression such as

``(~~a)

would be expanded (behind the curtains) as follows:

(clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/seq))
                  (clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/concat))
                  (clojure.core/list (clojure.core/seq (clojure.core/concat (clojure.core/list (quote clojure.core/list))
                  (clojure.core/list a)))))))))

and then evaluated, producing;

(clojure.core/seq (clojure.core/concat (clojure.core/list 5)))

Of course the same expression could also be equivalently expanded as

(clojure.core/list `list a)

which is indeed much easier to read. Clojure employs the former algorithm which is more generally applicable in cases where there is also splicing.

The principle is that the result of an expression with syntax-quotes nested to depth k is the same only after k successive evaluations are performed, regardless of the expansion algorithm (Guy Steele).

For Vectors, Maps, and Sets we have the following rules:

`[x1 x2 x3 ... xn]  ; is interpreted as (clojure.core/apply clojure.core/vector `(x1 x2 x3 ... xn))
`{x1 x2 x3 ... xn}  ; is interpreted as (clojure.core/apply clojure.core/hash-map `(x1 x2 x3 ... xn)) 
`#{x1 x2 x3 ... xn} ; is interpreted as (clojure.core/apply clojure.core/hash-set `(x1 x2 x3 ... xn))

Nested Syntax-quotesEdit

Syntax-quote is easiest to understand if we define it by saying what a syntax-quoted expression returns. To evaluate a syntax-quoted expression, you remove the syntax-quote and each matching tilde, and replace the expression following each matching tilde with its value. Evaluating an expression that begins with a tilde causes an error.

A tilde matches a syntax-quote if there are the same number of tildes as syntax-quotes between them, where b is between a and c if a is prepended to an expression containing b, and b is prepended to an expression containing c. This means that in a well-formed expression the outermost syntax-quote matches the innermost tilde(s).

Suppose that x evaluates to user/a, which evaluates to 1; and that y evaluates to user/b, which evaluates to 2.

You can prepare that from the REPL as follows:

user=> (def x `a)
user=> (def y `b)
user=> (def a 1)
user=> (def b 2)

To evaluate the expression

``(w ~x ~~y )

we remove the first syntax-quote and evaluate what follows any matching tilde. The rightmost tilde is the only one that matches the first syntax-quote. If we remove it and replace the expression it's prepended to, y, with its value, we get:

`(w ~user/x ~user/b)

Notice how x got "resolved" to user/x. Any unqualified symbol gets resolved in Clojure! This differentiates it (for the better) from Common Lisp.

In this latter expression, both of the tildes match the syntax-quote, so if we were to evaluate it in turn, we would get:

(w user/a 2)

A tilde-at (~@) behaves like a tilde, except that the expression it's prepended to must both occur within and return a list or sequence in general. The elements of the returned sequence are then spliced into the containing sequence. So

``( w ~x ~~@(list `a `b))

evaluates to

`(w ~user/x ~user/a ~user/b)

User-defined Reader MacrosEdit

At this time, Clojure does not allow you to define your own reader macros, but this may change in the future.