Clojure 的 eval 没有 "see" 本地符号

Clojure's eval does not "see" local symbols

我正在 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: Unable to resolve symbol: sequ in this context, compiling:(/tmp/form-init3253735970468294203.clj:1:25)

我如何定义符号,使它们通过 eval "seen"?

Eval 不识别词法绑定(本地的,比如 let),但它识别 global/dynamic 的。因此,解决方案之一是在动态 binding 上下文中预定义动态变量和 eval

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) 总是 returns 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"

向 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")

通过宏的邪恶魔法,您实际上可以构建一个 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"

您的程序中还有一个小错误。使用单个参数调用 < 将始终 return 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"]