我有一个像这样定义的函数规范,我想把它评估成一个函数对象,所以我可以传递。
(def spec '(foo [n] (* 2 n)))
我可以像这样创建一个宏
(defmacro evspec [name arg & body] `(defn ~name [~arg] ~@body))
然后以下调用将给我函数foo。当用3调用时,(foo 3)将返回6.
(evspec foo n (* 2 n))
但是,如果我从上面定义的规范中获取函数体,则返回的函数foo不会评估体形(* 2 n),而是返回体形。
(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是邪恶的。
答案 0 :(得分:5)
如果没有eval
,一般不可能这样做。宏只是一个函数,它在编译时从字面上传递它的参数表达式(通常根本不可能知道它们的值在运行时可能是什么)。特别是,在问题文本中evspec
表单内的let
调用中,返回值为(* 2 n)
,evspec
宏扩展器会在字面上看到符号{{ 1}}和符号foo
作为其位置参数,n
(包含单符号(body)
的seq)作为其“rest”参数;返回值与这些输入一致。
然而,使用body
来达到这种目的是完全没问题的。重要的是要记住它具有相当大的运行时成本,所以你需要稍微使用它,但是一旦你使用eval
生成一个函数,它就是一个非常好的Clojure函数,就像它一样快任何其他。
另外,请注意,虽然在JavaScript eval
中对文本进行操作,但Clojure的eval
在Clojure数据结构上运行 - 实际上相同的数据结构宏操作 - 这可以说它不易出错。