cfgauss
cfgauss

Reputation: 69

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?

Upvotes: 2

Views: 308

Answers (2)

peter pun
peter pun

Reputation: 394

A correct form for the macro you want to write is the following:

(defmacro sym-def [s v] `(def ~(eval s) ~v))

... or equivalently:

(defmacro sym-def [s v] (list `def (eval s) v))

You just need to evaluate the first argument inside the macro, because a macro's arguments are not evaluated when it is applied. If the only symbol-producing expressions you are going to use are calls to symbol, you may prefer the following macro:

(defmacro defsym [s v] (list `def (symbol s) v))

... and a companion to it:

(defmacro sym [s] (symbol s))

These macros translate a string or symbol to a symbol. Here are some examples of their use:

(defsym "the first natural number" 0)
;=> #'user/the first natural number
(sym "the first natural number")
;=> 0
(defsym pi 3.14159) ;same as: (def pi 3.14159)
;=> #'user/pi
(sym pi) ;same as: pi
;=> 3.14159

The variations given below might also be usefull:

(defmacro defsym* [s v] (list `def (symbol (eval s)) v))
(defmacro sym* [s] (symbol (eval s)))

They translate a string/symbol-producing expression to a symbol after evaluating it. Here are some examples of defsym*'s use:

(defsym* "abc" "xyz")
(defsym* (str \a \b \c) "xyz")
(defsym* (symbol "abc") "xyz")
(defsym* 'abc "xyz")
;all the previous are equivalent and what follows is valid for any of them
;=> #'user/abc
abc
;=> "xyz"
(defsym* abc 0)
;=> #'user/xyz
abc
;=> "xyz"
xyz
;=> 0

Upvotes: 2

amalloy
amalloy

Reputation: 91847

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.

Upvotes: 9

Related Questions