Clojure Reader Macros

Unlike Common Lisp, Clojure doesn't support user-defined reader macros. You can read some of the rationale for why in this chat log, among other places. I think that's probably a good decision; I don't see a lot of need for mangling the reader. Regular macros get you pretty far already and Clojure has built-in reader support for all the good stuff.

But how hard would it be to have custom reader macros in Clojure if you wanted them? Turns out not too hard if you're willing to ruthlessly break encapsulation and rely on implementation details. Here's one way you could define a dispatch reader macro (i.e. one starting with # and some specified second character):

(defn dispatch-reader-macro [ch fun]
  (let [dm (.get (doto (.getDeclaredField clojure.lang.LispReader "dispatchMacros")
                   (.setAccessible true))
                 nil)]
    (aset dm (int ch) fun)))

Pass in a character and an fn and you get a reader macro. For a silly example let's make reader syntax to uppercase a literal string.

(defn uppercase-string [rdr letter-u]
  (let [c (.read rdr)]
    (if (= c (int \"))
      (.toUpperCase (.invoke
                     (clojure.lang.LispReader$StringReader.)
                     rdr
                     c))
      (throw (Exception. (str "Reader barfed on " (char c)))))))

The function is passed a reader and the dispatch character (which you can usually ignore). I cheat and use Clojure's StringReader to do the real work.

Now I can do this:

user> (dispatch-reader-macro \U uppercase-string)
#<user$uppercase_string__1295 user$uppercase_string__1295@9b59a2>

user> #U"Foo bar BAZ"
"FOO BAR BAZ"

user> (println #U"foo\nbar")
FOO
BAR
nil

user> #U(blarg)
java.lang.Exception: Reader barfed on (

(= "FOO" "foo")
false

(= "FOO" #U"foo")
true

Oh sweet Jesus don't use this in real code, because:

  1. The community will rightly hunt you down with torches and pitchforks.
  2. Reader macro characters are reserved and may conflict with later changes to the core language.
  3. These are set globally, not per-namespace.
  4. And so on. Just don't.

But I think it's a nice demonstration. I've read opinions that Clojure isn't a Real Lisp™ because a lot of Clojure is written in Java and isn't extensible in Clojure itself, but that's generally not true. The reader code for Clojure was all written in Java, but above I modify it from Clojure. There is no line separating Java-land and Clojure-land. It's all one big happy family.

3 Comments

http://gravatar.com/avatar/2d7ff6000f9560e1fd7b3e9c890ae69d.jpg?d=identicon
rodjure says:

Doesn't work any more with clojure 1.1 alpha:

Clojure 1.1.0-alpha-SNAPSHOT user=> (defn dispatch-reader-macro [ch fun] (let [dm (.get (.getDeclaredField clojure.lang.LispReader "dispatchMacros") ni l)] (aset dm (int ch) fun)))

'user/dispatch-reader-macro

user=> (defn uppercase-string [rdr letter-u] (let [c (.read rdr)] (if (= c (int \")) (.toUpperCase (.invoke (clojure.lang.LispReader$StringReader.) rdr c)) (throw (Exception. (str "Reader barfed on " (char c)))))))

'user/uppercase-string

user=> (dispatch-reader-macro \U uppercase-string) java.lang.IllegalAccessException: Class user$dispatch_reader_macro__463 can not access a member of class clojure.lang.LispReader with modifiers "static" (NO_SOU RCE_FILE:0)

Oct 29, 2009 06:08 PM PST
http://gravatar.com/avatar/2d7ff6000f9560e1fd7b3e9c890ae69d.jpg?d=identicon
rodjure says:

worked with this slight mod, could just be a glitch in the alpha version.

(defn dispatch-reader-macro [ch fun] (def a (.getDeclaredField clojure.lang.LispReader "dispatchMacros") ) (let [dm (.get a nil)] (aset dm (int ch) fun)))

Oct 29, 2009 06:40 PM PST
http://gravatar.com/avatar/4d84ec3981443dfd9c287e845b60d2ce.jpg?d=identicon
Brian says:

I updated the code a bit so it works now.

Dec 16, 2009 06:55 PM PST

Speak Your Mind

This says COWS.

Preview

Commenting Help

Email / Avatar

  • Supply your email address and your Gravatar will be used.
  • I will never email you and your address won't be published.

No HTML allowed!

All HTML is auto-escaped. Use Markdown. Examples:

  • *emphasis* = emphasis
  • **strong** = strong
  • [link](http://foo.bar) = <a href="http://foo.bar">link</a>
  • `code in backticks` = code in backticks
  •     code indented 4 spaces =
    code indented four spaces
  • > Angle-brace quoted text =
    Angle-brace quoted text