5

我有一个这样定义的函数规范,我想将它评估为一个函数对象,以便我可以传递。

(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 是邪恶的。

4

1 回答 1

6

如果没有eval. 宏只是一个函数,它在编译时按字面意思传递其参数表达式(通常根本不可能知道它们在运行时的值可能是什么)。特别是,在问题文本中对表单evspec内部的调用let中,返回值为(* 2 n)evspec宏扩展器将符号foo和符号n视为其位置参数,并将(body)(包含单个符号的 seq body)视为其“休息”参数; 返回值与这些输入一致。

但是,eval用于这种目的是完全可以的。重要的是要记住,它有相当大的运行时成本,所以你会想稍微谨慎地使用它,但是一旦你使用 生成一个函数eval,它就是一个非常好的 Clojure 函数,和其他任何函数一样快。

另外,请注意,虽然在 JavaScript 中eval对文本进行操作,但 Clojureeval对 Clojure 数据结构进行操作——实际上与宏操作的数据结构相同——这可以说使其更不容易出错。

于 2013-07-10T00:48:22.080 回答