Wishful Coding

Didn't you ever wish your computer understood you?

Can't take value of a macro

Do. not. ever. make me see that exception. Because in my opinion that means you failed at designing your shiny DSL.

A macro is not a thing, but a transform from one thing to another. If your DSL does not have things – domain specific values, as Christophe Grand calls them – at its core, you’re doing it wrong.

First and foremost, a macro should be simple, where simple means ‘not compound’, as explained by Stuart Halloway.

I would like to define a simple macro as one that is written as a compound of simple functions returning code that evaluates to a simple or composite value.

  • (+ 1 1) returns a simple value
  • (a-record. 1 2 3) evaluates to a composite of simple things
  • (do (def foo 2) (def bar 4) (do-thing-to foo)) is not simple

Another good non-reason to use macros is to defer execution.

(defmacro assoc-once [m k v]
  `(if-not (get ~m ~k)
     (assoc ~m ~k ~v)
     ~m))

(assoc-once {:a 1} :a (println "foo"))
; {:a 1}
(assoc-once {:a 1} :b (println "foo"))
; foo
; {:b nil, :a 1}

Works nicely, uh? Now assume we have an atom, try (swap! a assoc-once ...) It’ll honor our post title: Can’t take value of a macro

You know what else defers execution? A function.

(defn assoc-once [m k v]
  (if-not (get m k)
    (assoc m k (v)) ; note parens
    m))

(assoc-once {:a 1} :a #(println "foo"))
; {:a 1}
(assoc-once {:a 1} :b #(println "foo"))
; foo
; {:b nil, :a 1}

This is of course not as good-looking as the macro, but the swap! case works fine here. If you care enough, you could define a macro on top of the function, giving you a simple macro.

(defmacro assoc-once [m k v]
  `(assoc-once* ~m ~k (fn [] ~v)))

Now, this is a very long and opinionated post for me already, so I’m going to stop here. I highly recommend that you watch the 2 video’s I linked to.

I had in mind to take you through another example of using protocols and function composition to simplify and avoid macros, but I’ll just give you this, this and that as a homework assignment.

Pepijn de Vos