在clojure中,我们可以使用非引号切片~@
来传播列表。例如
(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
是#34;应用"的唯一方法在运行时列出特殊形式以使用非引号切片和eval
?
答案 0 :(得分:7)
Clojure有一个简单的模型,说明如何加载和执行程序。略有简化,它是这样的:
读者从文本流中读取一些源代码;
一次将一个表单传递给编译器;
编译器扩展它遇到的任何宏;
对于非宏,编译器应用各种简单的评估规则(特殊形式的特殊规则,文字评估给自己,函数调用如此编译等);
评估已编译的代码,并可能更改以下表单使用的编译环境。
语法quote是一个读者功能。它在读取时由发出列表结构的代码替换:
;; 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))))
只有在语法引用的块的上下文中,读者才能提供~
和~@
这种特殊处理,并且语法引用的块总是生成可以调用少数seq-building的形式函数来自clojure.core
,并且由引用数据组成。
这一切都是从上面列表中的第1步开始的。因此,对于类似apply
的机制,语法引用非常有用,您需要它在流程中的那一点生成正确形状的代码,然后看起来像所需的“apply
结果“在后续步骤中。如上所述,syntax-quote总是生成创建列表结构的代码,特别是它永远不会返回看起来像不带引号的do
或if
等的不带引号的表达式,所以这是不可能的。
这不是问题,因为给定上述执行模型的合理代码转换可以使用宏来实现。
顺便提一下,macroexpand
调用在您的示例中实际上是多余的,因为语法引用的表单 与其宏展开相同(因为它应该是,因为{{1} }不是宏):
+