以下代码来自 Houser Fogus 的The Joy of Clojure(第二版)第 8.1.1 章:
(defn contextual-eval [ctx expr]
(eval
`(let [~@(mapcat (fn [[k v]] [k `'~v]) ctx)] ; Build let bindings at compile time
~expr)))
(contextual-eval '{a 1, b 2} '(+ a b))
;;=> 3
(contextual-eval '{a 1, b 2} '(let [b 1000] (+ a b)))
;;=> 1001
我不太明白构造`'~v
的含义。有人可以详细说明一下吗?
书中只说了
The bindings created use the interesting `'~v pattern to garner the value of the built bindings at runtime.
例如
(contextual-eval '{a 1, b 2} '(+ a b))
扩展为
(let [a '1 b '2] (+ a b)))
我不明白为什么要引入这些引言,它们有什么用处。
此外,我们还有以下行为:
(contextual-eval '{a 1, b (+ a 1)} '(+ a b))
ClassCastException clojure.lang.PersistentList cannot be cast to java.lang.Number
(defn contextual-eval' [ctx expr]
(eval
`(let [~@(mapcat (fn [[k v]] [k v]) ctx)]
~expr)))
(contextual-eval' '{a 1, b (+ a 1)} '(+ a b))
;=> 3
最佳答案
该表达式使用了 Clojure 中几乎所有可用的特殊线噪声符号,因此值得将其分开:
`
是“syntax-quote”的读取器宏
“syntax-quote”在阅读器宏中很特殊,因为您只能通过其简短形式来调用该函数。例如,您不能调用类似(syntax-quote some-here )
的内容,而是可以编写`something-here
。它提供了一组丰富的选项,用于指定应计算表达式后的哪些部分以及应按字面意思理解哪些部分。'
是quote
特殊形式的读取器宏快捷方式。它导致它所包装的表达式不被求值,而是被视为数据。如果您想编写一个文字quote
形式而不对其求值,您可以编写`'something
来获取`(quote some)
作为结果。这将导致生成的引用表达式不被计算,只是按原样返回而不运行它。~
是syntax-quote语法的一部分(它是带有语法的“引用”),意思是“实际上让这部分运行”,所以如果你有一个很大的列表你想从字面上理解(现在不运行),除非你现在确实想要评估一个项目,那么你可以写`(a b c ~d e f g)
并且 d 将是唯一的东西该列表将被评估为当前定义的内容。
现在我们可以把它们放在一起:
`'~
表示“创建一个包含 v 现在的值的引号表达式”
user> (def v 4)
#'user/v
user> `'~v
(quote 4)
关于这种幻想的动机:
(contextual-eval '{a 1, b 2} '(+ a b))
似乎只是添加一些额外的思考而没有任何好处,因为它基本上只是引用值 1 和 2。因为这些是正确的“值”,所以它们无论如何都不会改变。
现在如果表达式是:
(contextual-eval
'{a (slurp "https://example.com/launch?getCode")
b the-big-red-button}
'(press b a))
那么,在何时运行该特定代码时要小心会更有意义。所以这个模式是关于控制程序生命周期的哪个阶段实际运行代码。 Clojure 有几个代码可以运行的“时间”:
- 在宏观评估时:在代码形成时。 (这里的副作用需要深思熟虑)。
- 加载命名空间时:这是顶层表单运行的时间。这种情况通常发生在您启动程序时和调用
main
之前。 - 由于运行
main
而运行的东西
ps:上述定义是根据该问题的上下文量身定制的,无意使用“官方”术语。
关于clojure - 在《The Joy of Clojure》中的 contextual-eval 中取消引用构造,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35039932/