我正在Clojure中试验eval:
(let [code_as_data '(if (< sequ) on_true on_false)
sequ [1 3 5]
on_true "sequence is sorted in ascending order"
on_false "sequence is NOT sorted"]
(eval code_as_data))
CompilerException java.lang.RuntimeException:无法在此上下文中解析符号:sequ,编译:(/ tmp / form-init3253735970468294203.clj:1:25)
如何定义符号以便eval“看到”?
答案 0 :(得分:2)
Eval不识别词法绑定(本地词汇,如let
),但它识别全局/动态词汇绑定。因此,其中一个解决方案是在动态eval
上下文中预定义动态变量和binding
:
user> (def ^:dynamic sequ)
#'user/sequ
user> (def ^:dynamic on_true)
#'user/on_true
user> (def ^:dynamic on_false)
#'user/on_false
user>
(let [code_as_data '(if (apply < sequ) on_true on_false)]
(binding [sequ [1 3 5]
on_true "sequence is sorted in ascending order"
on_false "sequence is NOT sorted"]
(eval code_as_data)))
"sequence is sorted in ascending order"
(注意一个小错误:你使用(< sequ)
总是返回true
,你需要的是(apply < sequ)
)
正如你所看到的,它非常丑陋,你真的不想使用它。 其中一种可能的解决方法是使用语法引用将数据替换为已评估的代码:
user>
(let [sequ [1 3 5]
on_true "sequence is sorted in ascending order"
on_false "sequence is NOT sorted"
code_as_data `(if (apply < ~sequ) ~on_true ~on_false)]
(eval code_as_data))
"sequence is sorted in ascending order"
另一个选项(看起来对我来说更有用)是使用walker将所需的所有符号替换为它们的值:
user>
(let [code_as_data '(if (apply < sequ) on_true on_false)
bnd {'sequ [1 3 5]
'on_true "sequence is sorted in ascending order"
'on_false "sequence is NOT sorted"}]
(eval (clojure.walk/postwalk-replace bnd code_as_data)))
"sequence is sorted in ascending order"
答案 1 :(得分:2)
为eval在运行时生成的代码提供本地数据的最简单方法是生成一个带参数的表单。
(let [code-as-data '(fn [sequ on-true on-false]
(if (apply < sequ)
on-true
on-false))
f (eval code-as-data)]
(f [1 3 5]
"sequence is sorted in ascending order"
"sequence is NOT sorted"))
当然,由于函数是我们将运行时值插入已知表单的标准方法,因此根本不需要使用eval。没有eval
:
(let [f (fn [sequ on-true on-false]
(if (apply < sequ)
on-true
on-false))]
(f [1 3 5]
"sequence is sorted in ascending order"
"sequence is NOT sorted"))
在实际代码中,只有在运行时需要生成逻辑时才需要eval
版本(例如,如果用户提供新算法)。如果期望用户将其代码编写为函数是繁重的,那么您可以做出妥协:
(defn code-with-context
[body sq t else]
(let [f (eval (list 'fn '[sequ on-true on-false] body))]
(f sq t else)))
(code-with-context (read-string "(if (apply < sequ) on-true on-false)")
[1 3 5]
"sequence is sorted in ascending order"
"sequence is NOT sorted")
答案 2 :(得分:0)
通过宏的邪恶魔力,您实际上可以构建一个 eval
版本,它主要按照您的意愿行事。
(defmacro super-unsafe-eval
"Like `eval`, but also exposes lexically-bound variables to eval. This
is almost certainly a bad idea."
[form]
`(eval (list 'let
~(vec (mapcat #(vector `(quote ~%)
`(list 'quote ~%))
(keys &env)))
~form)))
这个宏使用特殊的 &env
变量来访问本地环境。然后构造一个 let
形式,该形式绑定当前在宏展开环境中绑定的所有名称。这使您的代码示例工作:
(let [code_as_data '(if (< sequ) on_true on_false)
sequ [1 3 5]
on_true "sequence is sorted in ascending order"
on_false "sequence is NOT sorted"]
(super-unsafe-eval code_as_data))
;;=> "sequence is sorted in ascending order"
你的程序也有一个小错误。使用单个参数调用 <
将始终返回 true
。您需要使用 apply
才能使其正常工作:
(let [code_as_data '(if (apply < sequ) on_true on_false)
on_true "sequence is sorted in ascending order"
on_false "sequence is NOT sorted"]
[(let [sequ [1 3 5]]
(super-unsafe-eval code_as_data))
(let [sequ [1 3 1]]
(super-unsafe-eval code_as_data))])
;;=> ["sequence is sorted in ascending order" "sequence is NOT sorted"]