Clojure: def discards ^:macro when used outside the top-level

If I evaluate

(def ^:macro my-defn1 #'defn)

a macro named 'my-defn1' is defined, which I can use just like 'defn'.

However, if I evaluate instead

(if true
  (def ^:macro my-defn2 #'defn))

the var for 'my-defn2' doesn't have the :macro metadata set and I can't use it as a macro, even though the 'def' form is equal to the previous case.

Here is the complete code (http://cljbin.com/paste/52322ba5e4b0fa645e7f9243):

(def ^:macro my-defn1 #'defn)

(if true
  (def ^:macro my-defn2 #'defn))

(println (meta #'my-defn1))    ; => contains :macro

(println (meta #'my-defn2))    ; => doesn't contain :macro!

(my-defn1 hello1 []
          (println "hello 1"))

(hello1)                       ; => prints "hello 1"

(my-defn2 hello2 []            ; => CompilerException: Unable to resolve 
  (println "hello 2"))         ;    symbol: hello2 in this context

What makes the behaviour different?

Upvotes: 3

Views: 188

Answers (1)

Joost Diepenmaat
Joost Diepenmaat

Reputation: 17773

Clojure's def cannot really be conditionally applied. The documentation for def is IMO insufficiently strong on that part. It's not just bad style, it can cause all kinds of subtle issues.

You should only use def at the top-level, or in a do or let form at the top-level. Conditionally applying def will result in the functionality being split up in something like declare and a subsequent conditional def, but not always in the way that you'd expect/like.

You might be better off using def at the top level here and then conditionally using alter-var-root. Or use (def my-var (if .. .. ..)). Think about why you'd ever want to have a global definition "disappear".

Upvotes: 1

Related Questions