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.