如何在clojure中将列表形式转换为函数



我有一个这样定义的函数规范,我想把它求值成一个函数对象,这样我就可以四处传递了。

(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数据结构进行操作——实际上与宏操作的数据结构相同——这可以说使其不太容易出错。

最新更新