Clojure的eval不会“看到”本地符号

时间:2016-07-04 11:09:58

标签: clojure eval read-eval-print-loop

我正在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“看到”?

3 个答案:

答案 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"]