clojure 中的上下文评估

qed*_*qed 5 macros clojure

这是Clojure第8章中的一个例子:

(defn contextual-eval [ctx expr]
  (let [new-expr
        `(let [~@(mapcat (fn [[k v]]
                           [k `'~v])
                         ctx)]
           ~expr)]
    (pprint new-expr)
    (eval new-expr)))
(pprint (contextual-eval '{a 1 b 2} '(+ a b)))
Run Code Online (Sandbox Code Playgroud)

我觉得 ``'` 部分很令人困惑,它是做什么用的?

我也尝试稍微修改一下函数:

(defn contextual-eval [ctx expr]
  (let [new-expr
        `(let [~@(mapcat (fn [[k v]]
                           [k `~v])
                         ctx)]
           ~expr)]
    (pprint new-expr)
    (eval new-expr)))
(pprint (contextual-eval '{a 1 b 2} '(+ a b)))


(defn contextual-eval [ctx expr]
  (let [new-expr
        `(let [~@(vec (apply 
                        concat 
                        ctx))]
           ~expr)]
    (pprint new-expr)
    (eval new-expr)))
(pprint (contextual-eval '{a 1 b 2} '(+ a b)))
Run Code Online (Sandbox Code Playgroud)

以上所有版本都有类似的效果。那作者为什么选择用`'呢?

更详细的外观:

(use 'clojure.pprint)
(defmacro epprint [expr]
  `(do
     (print "==>")
     (pprint '~expr)
     (pprint ~expr)))
(defmacro epprints [& exprs]
  (list* 'do (map (fn [x] (list 'epprint x))
                  exprs)))

(defn contextual-eval [ctx expr]
  (let [new-expr
        `(let [~@(mapcat (fn [[k v]]
                           (epprints
                             (class v)
                             v
                             (class '~v)
                             '~v
                             (class `'~v)
                             `'~v
                             (class ctx)
                             ctx)
                           [k `~v])
                         ctx)]
           ~expr)]
    (pprint new-expr)
    (eval new-expr)))
(pprint (contextual-eval '{a (* 2 3) b (inc 11)} '(+ a b)))
Run Code Online (Sandbox Code Playgroud)

这会在 repl 中打印出以下内容:

==>(class v)
clojure.lang.PersistentList
==>v
(* 2 3)
==>(class '~v)
clojure.lang.PersistentList
==>'~v
~v
==>(class
 (clojure.core/seq
  (clojure.core/concat
   (clojure.core/list 'quote)
   (clojure.core/list v))))
clojure.lang.Cons
==>(clojure.core/seq
 (clojure.core/concat (clojure.core/list 'quote) (clojure.core/list v)))
'(* 2 3)
==>(class ctx)
clojure.lang.PersistentArrayMap
==>ctx
{a (* 2 3), b (inc 11)}
==>(class v)
clojure.lang.PersistentList
==>v
(inc 11)
==>(class '~v)
clojure.lang.PersistentList
==>'~v
~v
==>(class
 (clojure.core/seq
  (clojure.core/concat
   (clojure.core/list 'quote)
   (clojure.core/list v))))
clojure.lang.Cons
==>(clojure.core/seq
 (clojure.core/concat (clojure.core/list 'quote) (clojure.core/list v)))
'(inc 11)
==>(class ctx)
clojure.lang.PersistentArrayMap
==>ctx
{a (* 2 3), b (inc 11)}
==>new-expr
(clojure.core/let [a (* 2 3) b (inc 11)] (+ a b))
18
Run Code Online (Sandbox Code Playgroud)

同样,对 v 使用单个语法引号似乎可以完成工作。

事实上,使用 `'v 可能会给你带来一些麻烦:

(defn contextual-eval [ctx expr]
  (let [new-expr
        `(let [~@(mapcat (fn [[k v]]
                           [k `'~v])
                         ctx)]
           ~expr)]
    (pprint new-expr)
    (eval new-expr)))
(pprint (contextual-eval '{a (inc 3) b (* 3 4)} '(+ a b)))

CompilerException java.lang.ClassCastException: clojure.lang.PersistentList cannot be cast to java.lang.Number, compiling:(/Users/kaiyin/personal_config_bin_files/workspace/typedclj/src/typedclj/macros.clj:14:22) 
Run Code Online (Sandbox Code Playgroud)

Leo*_*hin 2

`'~v 是一种返回方式

(list 'quote v)
Run Code Online (Sandbox Code Playgroud)

在这种情况下,引用表达式中 v 的实际值let,而不是符号本身。

IDK Clojure 的乐趣,但显然作者希望阻止 ctx 中传递的表单在扩展的 let 表单中被求值。例如 (contextual-eval '{a (+ 3 4)} 'a)将返回(+ 3 4),但7在您的版本中,它们的行为相同。