【问题标题】:Proper way of evaluating symbols in clojure macros在 clojure 宏中评估符号的正确方法
【发布时间】:2015-05-25 00:34:49
【问题描述】:

我有一个文件,其中包含一些受信任的 clojure 源代码:

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

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

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

如果我调用下面的马可

(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: Unable to resolve symbol: a in this context”

(请注意,我必须在宏中添加一个额外的eval 来获取符号e 的值,该符号在map 使用的匿名函数中使用)

(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))

非常感谢任何帮助!

【问题讨论】:

  • exprs绑定(+ a b),你调用eval就可以了,报错是正确的
  • 如果我在宏 create-fn 中添加 (println exprs),它会打印“e”。我的理解是,在宏扩展期间,exprs 绑定到符号e,而不是它的值。因此,我认为我需要在exprs 上调用eval 以产生符号e 的值...

标签: clojure macros


【解决方案1】:

我们看一下宏展开后的整个代码。这段代码:

(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))

为什么这不起作用?嗯,一个原因是a 和b 甚至不在(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 也不会看到 a 和 b。

您可以放弃宏,只需手动将代码修改为您可以评估的内容,如下所示:

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

或者,您可以将变量 a 和 b 声明为动态变量,然后在计算表达式之前使用 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))

如果您不想在命名空间中包含 a 和 b,您可以设置另一个命名空间和 evaluate the code there。


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

【讨论】:

  • 非常感谢您的详细解答!这有助于我在宏观评估期间理解词汇范围。很遗憾,我将使用非宏观解决方案,因为它看起来确实容易得多:-)
猜你喜欢
  • 2012-11-19
  • 1970-01-01
  • 2011-10-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多