我有一个包含一些可信的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))
非常感谢任何帮助!
答案 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))
为什么这不起作用?好吧,一个原因是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
和文件处理代码。