Search code examples
functionclojuremacros

Is there a Clojure macro equivalent to def?


I want to write a macro, sym-def, which has the same behavior as the special form def but uses (symbol "c"), say, as the first argument.

My first step was

(def (symbol "c") 4)

but this returned the error First argument to def must be a Symbol.

My second step was

(eval `(def ~(symbol "c") 4))

and this succeeded in defining c to be 4 in the global environment. Why did my first step fail while the second step succeeded?

Finally, I attempted to write the desired macro

(defmacro sym-def [sym value] `(def ~sym ~value))

but this has a "bad" macroexpand

(macroexpand '(sym-def (symbol "c") 4)) => (def (symbol "c") 4)

so that

(sym-def (symbol "c") 4)

fails with the same error as my first step.

What is the correct way to write the macro?


Solution

  • def does not evaluate its first argument. Imagine the chaos if it did! You couldn't write

    (def x 1)
    

    because it would first try to evaluate x, and fail because x is not yet defined! Now, since it doesn't evaluate its arguments, clearly it makes sense that

    (def (symbol "c") 4)
    

    doesn't work, just as

    (def 'c 4)
    

    wouldn't. def requires its first argument to be a literal symbol. You don't have a literal symbol, so you can't use def.

    But there is a lower-level mechanism to interact with the mappings in a namespace. In this case, you want clojure.core/intern:

    (intern *ns* (symbol "c") 4)
    

    intern is an ordinary function, so it evaluates all its arguments, meaning you can construct your var name in whatever crazy way you want. Then it adds to the given namespace a var mapping your symbol to its desired value.