在clojure 中,我们可以使用unquote 切片~@
来展开列表。例如
(macroexpand `(+ ~@'(1 2 3)))
扩展为
(clojure.core/+ 1 2 3)
重新排列语法时,这是宏中有用的功能。但是是否可以在宏之外使用非引号切片或熟悉的技术并且没有 eval
?
这是eval
的解决方案
(eval `(+ ~@'(1 2 3))) ;-> 6
但我宁愿做
(+ ~@'(1 2 3))
不幸的是,这会引发错误
IllegalStateException Attempting to call unbound fn: #'clojure.core/unquote-splicing clojure.lang.Var$Unbound.throwArity (Var.java:43)
一开始我以为apply
就可以了,用函数确实是这样
(apply + '(1 2 3)) ; -> 6
但是,宏或特殊形式不是这种情况。对于宏来说很明显,因为它在应用之前展开并且无论如何都必须是表单中的第一个元素。对于特殊形式,它虽然不是那么明显,但仍然有意义,因为它们不像函数那样是一等公民。例如以下抛出错误
(apply do ['(println "hello") '(println "world")]) ;-> error
是在运行时将列表“应用”到特殊形式以使用 unquote 切片和 eval
的唯一方法吗?
最佳答案
Clojure 有一个关于如何加载和执行程序的简单模型。稍微简化一下,它是这样的:
阅读器从文本流中读取一些源代码;
这一次以一种形式传递给编译器;
编译器扩展它遇到的任何宏;
对于非宏,编译器应用各种简单的求值规则(特殊形式的特殊规则、字面值对它们自己求值、函数调用按原样编译等);
编译后的代码被求值,可能会改变以下形式使用的编译环境。
语法引用是阅读器的一项功能。它在读取时被发出列表结构的代码替换:
;; note the ' at the start
user=> '`(+ ~@'(1 2 3))
(clojure.core/seq
(clojure.core/concat (clojure.core/list (quote clojure.core/+)) (quote (1 2 3))))
只有在语法引用 block 的上下文中,读者才能提供 ~
和 ~@
这种特殊处理,并且语法引用 block 总是产生可能的形式从 clojure.core
调用一些 seq 构建函数,否则由引用数据组成。
这一切都是上面列表中第 1 步的一部分。因此,要使 syntax-quote 用作类似于 apply
的机制,您需要它在流程的那个点生成正确形状的代码,然后看起来像所需的“apply
结果”在后续步骤中。如上所述,syntax-quote 总是生成创建列表结构的代码,特别是它永远不会返回看起来像未加引号的 do
或 if
等未加引号的表达式,所以那是不可能的。
这不是问题,因为在给定上述执行模型的情况下合理的代码转换可以使用宏来实现。
顺便说一下,macroexpand
调用在您的示例中实际上是多余的,因为语法引用形式已经 与其宏扩展相同(它应该是这样,因为 +
不是宏):
user=> `(+ ~@'(1 2 3))
(clojure.core/+ 1 2 3)
关于Clojure,在语法引用之外取消引用切片,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44052107/