【问题标题】:Clojure's eval does not "see" local symbolsClojure 的 eval 不会“看到”局部符号
【发布时间】:2016-07-04 11:09:57
【问题描述】:

我正在 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, 正在编译:(/tmp/form-init3253735970468294203.clj:1:25)

如何定义符号以便 eval “看到”它们?

【问题讨论】:

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


    【解决方案1】:

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

    【讨论】:

    • 我对从部件构造表达式的能力感兴趣。这些部分可以来自各种来源。我看不到像您的示例中那样评估预定义的静态表达式的实用程序。我错过了什么吗?我理解用 ~ 取消引用可以让您生成/组装表达式,就像在 letwinskis 的示例中一样。
    • 类似:(defn useless_generator [predicate on_true on_false] `(if ~predicate ~on_true ~on_false)) (eval (useless_generator '(&lt; 2 3 1) "sorted" "unsorted")) '
    • @Sharas 我不明白哪一方面需要评估
    • 另外,我编辑显示使用字符串(可能来自用户输入)
    【解决方案2】:

    Eval 不能识别词法绑定(本地绑定,如 let),但它可以识别全局/动态绑定。所以解决方案之一是在动态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"
    

    (注意一个小错误:你使用(&lt; sequ),它总是返回true,你需要的是(apply &lt; 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 创建一个函数,并将局部变量作为参数传递给函数,调用函数
    • ((eval '(fn [sequ on_true on_false] (if (apply &lt; sequ) on_true on_false))) [1 3 5] "ascending" "descending")
    • @noisesmith,这也不错。
    • @noisesmith,如果我是你,我会回答的。也许它更符合 op 的目的
    【解决方案3】:

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

    此宏使用特殊的&amp;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"
    

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

    【讨论】:

      猜你喜欢
      • 2020-02-27
      • 1970-01-01
      • 2013-03-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-08-08
      • 1970-01-01
      相关资源
      最近更新 更多