在 clojure 宏中评估符号的正确方法
Proper way of evaluating symbols in 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: 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))
非常感谢任何帮助!
让我们看看宏扩展后的整个代码。此代码:
(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
.
我有一个包含一些受信任的 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: 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))
非常感谢任何帮助!
让我们看看宏扩展后的整个代码。此代码:
(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
.