Clojure, Java

Metaprogramming with Clojure

So at one of the recent Clojure dojos we had a situation where we had a number of functions to do with moving that essentially involved passing different keys to a map under different symbols that could be used in the REPL.

Well in Ruby this is the kind of thing you would do by metaprogramming abstracting the shared code into a single function that gets mapped under many names.

During the dojo Tom Crayford provided a map function that seemed to correctly generate the functions we wanted but did so under anonymous names. We couldn’t lick the problem during the dojo and it really drove me a little mad as it seemed we were very close. Some people at the dojo were talking about macros but it seemed that was too strong for the short distance we had to cover to complete the task. It also felt like there was an issue with the language if it couldn’t do this.

After four hours, much consulting of the Halloway book and some fierce googling, a question at Stack Overflow put me on the trail on intern. Adding this to Tom’s function resulted in the right functionality. Here’s a simplified example. However when I ran the code into the REPL the functions failed to appear.

Clojure namespaces are very dynamic and it seems they are very amenable to the kind of manipulation I wanted to do. ns-interns gives you a list of the functions that are currently in a namespace so it was possible to confirm that the functions really had failed to appear. Running the code in the REPL did add them though. So the code was correct.

The final piece of the puzzle was the lazy sequence issue that had been mentioned at the dojo. The map function was being immediately evaluated in the REPL but I had to add a doall to make the sequence unwind when it was invoked during the namespace loading.

The relevant code is part of my Clork fork.

The worst thing in trying to make this work is the old school nature of the Clojure documentation. A lot of the functions tells you literally what the function does but not why you might want to use or more critically give examples of the expected usage. Clojure supports documenting metadata but I think it really needs to be used more effectively.

Standard

7 thoughts on “Metaprogramming with Clojure

  1. John Cromartie says:

    I was doing this not too long ago, mapping a “big ball of fns” (in the form of a hash map) into the current namespace to add data model accessors to a lib dynamically. In the end I realized that this was a Very Bad Thing(tm), mostly because Clojure is NOT Ruby. The Clojure community values code that reads well, and metaprogramming does not usually read well. The fns in a namespace should be spelled out and documented, and you should be able to know what a lib does easily by looking at the definitions in it.

  2. Chousuke says:

    Doing this at runtime is unnecessary and ugly. It’s much cleaner to do this with a macro like so:

    (defmacro generate-movement-functions
    [& name-dir-pairs]
    `(do
    ~@(for [[name dir] (partition 2 name-dir-pairs)]
    `(defn ~name [] (move-to ~dir)))))

    which is used as:
    (generate-movement-functions north :n, south :s, east :s, west :w)

    It’s a bit more typing for you, but it’s also more idiomatic and as such easier to understand, and significantly less hacky.

    Since clojure has macros, generating global definitions during runtime is almost always the wrong thing to do. (Except in an interactive REPL, of course)

    • Thanks for the example, I tried to make this work before returning to the map and intern. The big issue was that I couldn’t figure out the significance and use of the back ticks and atildas. The at dereference was a surprise too.

  3. Chousuke says:

    Forgot to mention: Single-segment namespaces can cause problems, and they should be avoided. I realise it’s not all your code, but you should know.

    Also, the anonymous function in the swap! call in move-to is unnecessary. swap! will pass any additional arguments to the function after the atom’s value, so so you can use the idiomatic form:

    (swap! the-player move-player rooms direction)

  4. Frank says:

    I think this IS a wonderful piece of work demonstrating what I would call “Dynamic DSL Generation” because that so happens to be what I am developing. It produces idiomatic clojure using data to drive the productions.

    I will certainly reference this in my project which is doing the same using conceptual graphs as the data source!

    Bravo and ignore the zealots!

Leave a comment