评估clojure宏中符号的正确方法

时间:2015-03-21 18:10:35

标签: clojure macros

我有一个包含一些可信的clojure源代码的文件:

((+ a b) (* a b) (- a b))

对于列表中的每个项目,我想生成一个匿名函数:

(fn [a b] (+ a b))
(fn [a b] (* a b))
(fn [a b] (- a b))

如果我打电话给以下marco

(defmacro create-fn
  [args exprs]
    `(fn ~args ~exprs))

直接用一些clojure代码完美地运作:

user=> (macroexpand-1 '(create-fn [a b] (* a b)))
(clojure.core/fn [a b] (* a b))

但是当我将文件的上下文绑定到本地并尝试映射我的宏时,它将无法工作。在访问第一个生成的函数时,我收到错误消息" java.lang.RuntimeException:无法解析符号:a在此上下文中"

(请注意,我必须在宏中添加额外的eval以获取在地图使用的匿名函数中使用的符号e的值)

(defmacro create-fn
  [args exprs]
   `(let [e# (eval ~exprs)]
     (fn ~args
       e#)))

(let [exprs (read-string "((+ a b) (* a b) (- a b))")
      fns   (map
             (fn [e] (create-fn [a b] e))
             exprs)]
  (first fns))

非常感谢任何帮助!

1 个答案:

答案 0 :(得分:3)

让我们看看宏扩展后的整个代码。这段代码:

(let [exprs (read-string "((+ a b) (* a b) (- a b))")
      fns   (map
             (fn [e] (create-fn [a b] e))
             exprs)]
  (first fns))

扩展为此,其中e__900__auto__e#生成的符号:

(let [exprs (read-string "((+ a b) (* a b) (- a b))")
      fns   (map
             (fn [e] (let [e__900__auto__ (eval e)]
                        (fn [a b] e__900__auto__))
             exprs)]
  (first fns))

为什么这不起作用?好吧,一个原因是ab甚至不在(eval e)的范围内。您可能想尝试下一步:

(defmacro create-fn [args exprs] `(fn ~args (eval ~exprs)))

扩展后,生成的函数如下所示:

(let [exprs (read-string "((+ a b) (* a b) (- a b))")
      fns   (map
             (fn [e] (fn [a b] (eval e)))
             exprs)]
  (first fns))

这看起来不错,但it won't work因为eval在空词法环境中进行评估。换句话说,即使使用此代码,eval也看不到ab

你可以放弃宏,只需手动将代码变成你可以评估的东西,如下所示:

(map
 (fn [e] (eval (concat '(fn [a b]) (list e))))
 exprs)

或者,您可以将变量ab声明为动态,然后在评估表达式之前使用binding来设置它们。

(declare ^:dynamic a ^:dynamic b)

(let [exprs (read-string "((+ a b) (* a b) (- a b))")
      fns   (map
             (fn [e] (fn [a1 b1] (binding [a a1 b b1] (eval e))))
             exprs)]
  (first fns))

如果您不想在命名空间中使用ab,则可以设置另一个命名空间evaluate the code there


我建议的解决方案不使用宏。它们在这里没用,因为宏在编译时被扩展,但表达式在运行时被读取。如果您确实想在此处使用宏,则需要在read-string内移动defmacro和文件处理代码。