我想编写一个宏std::atexit
,它的行为与特殊形式sym-def
相同,但是使用def
作为第一个参数。
我的第一步是
(symbol "c")
但是这返回了错误(def (symbol "c") 4)
。
我的第二步是
First argument to def must be a Symbol
并且成功将(eval `(def ~(symbol "c") 4))
定义为4。
为什么我的第一步失败了而第二步成功了?
最后,我尝试编写所需的宏
c
但这有一个“坏” (defmacro sym-def [sym value] `(def ~sym ~value))
macroexpand
这样
(macroexpand '(sym-def (symbol "c") 4)) => (def (symbol "c") 4)
失败,错误与我的第一步相同。
编写宏的正确方法是什么?
答案 0 :(得分:9)
def
不评估其第一个参数。想象一下,如果发生的话,将会造成混乱!你不会写
(def x 1)
因为它会首先尝试评估x
,但由于尚未定义x
而失败!现在,由于它不评估其参数,因此很明显
(def (symbol "c") 4)
不起作用,就像
(def 'c 4)
不会。 def
要求其第一个参数是文字符号。您没有文字符号,因此不能使用def
。
但是有一个较低级别的机制可以与名称空间中的映射进行交互。在这种情况下,您需要clojure.core/intern
:
(intern *ns* (symbol "c") 4)
intern
是一个普通函数,因此它会评估其所有参数,这意味着您可以以任何想要的疯狂方式构造您的var名称。然后它将一个var映射到给定的名称空间,将您的符号映射到其所需的值。
答案 1 :(得分:2)
您要编写的宏的正确格式如下:
(defmacro sym-def [s v] `(def ~(eval s) ~v))
...或等效地:
(defmacro sym-def [s v] (list `def (eval s) v))
您只需要评估宏中的第一个参数,因为应用宏时不会评估其参数。如果要使用的唯一产生符号的表达式是对symbol
的调用,则您可能更喜欢以下宏:
(defmacro defsym [s v] (list `def (symbol s) v))
...及其伴侣:
(defmacro sym [s] (symbol s))
这些宏将字符串或符号转换为符号。以下是其用法的一些示例:
(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
以下给出的变体也可能有用:
(defmacro defsym* [s v] (list `def (symbol (eval s)) v))
(defmacro sym* [s] (symbol (eval s)))
它们在评估字符串/符号产生表达式后将其转换为符号。以下是defsym*
用法的一些示例:
(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