我有一个这样定义的函数规范,我想把它求值成一个函数对象,这样我就可以四处传递了。
(def spec '(foo [n] (* 2 n)))
我可以创建一个类似的宏
(defmacro evspec [name arg & body] `(defn ~name [~arg] ~@body))
那么下面的调用将给我函数foo。当用3调用时,(foo3)将返回6。
(evspec foo n (* 2 n))
然而,如果我从上面定义的规范中获得函数体,那么返回的函数foo不会评估体形式(*2n),而是返回体形式。
(let [foo (first spec) arg (first (second spec)) body (last spec)]
(evspec foo arg body))
user=> (foo 3)
(* 2 n)
我注意到现在创建的foo函数是$eval$foo
user=> foo
#<user$eval766$foo__767 user$eval766$foo__767@39263b07>
而工作的foo函数是
user=> foo
#<user$foo user$foo@66cf7fda>
有人能解释一下为什么会有区别吗?我该怎么做?我想有一种方法不回复eval?来自javascript背景,不知何故,我一直认为eval是邪恶的。
如果没有eval
,一般情况下是不可能做到这一点的。宏只是一个函数,它在编译时直接传递其参数表达式(通常在运行时根本不可能知道它们的值)。特别地,在问题文本中对let
形式内的evspec
的调用中,其中返回值为(* 2 n)
,evspec
宏扩展器将符号foo
和符号n
视为其位置自变量,将(body)
(包含单个符号body
的序列)视为其"rest"自变量;返回值与这些输入一致。
然而,将eval
用于这种目的是完全可以的。重要的是要记住,它有相当大的运行时成本,因此您可能希望稍微节约使用它,但一旦您使用eval
生成了一个函数,它就是一个非常好的Clojure函数,与其他函数一样快。
此外,请注意,虽然在JavaScript中eval
对文本进行操作,但Clojure的eval
对Clojure数据结构进行操作——实际上与宏操作的数据结构相同——这可以说使其不太容易出错。