Clojure,语法引用之外的反引号切片

4
在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

在运行时“应用”列表的唯一方法是使用非引用切片和eval吗?

1个回答

8
Clojure有一个简单的程序加载和执行模型。稍微简化一下,它大概是这样的:
1. 读取一些源代码从文本流到阅读器; 2. 阅读器每次将其传递给编译器一个格式; 3. 编译器扩展遇到的任何宏; 4. 对于非宏,编译器应用各种简单的评估规则(特殊形式的特殊规则,文字量本身进行评估,函数调用被编译为这样等); 5. 编译后的代码被评估并可能改变以下格式使用的编译环境。
语法引用是一个阅读器功能。它在读取时由代码替换,以发出列表结构:
;; 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))))

只有在语法引用块的上下文中,阅读器才能为 `~` 和 `~@` 提供特殊处理;而语法引用块始终生成可以调用一个手段的几个序列构建函数的形式,这些函数来自 `clojure.core`,并且以引用数据作为其它组成部分。
所有这些都是在上述列表中的第一步骤中发生的。因此,为了使语法引用类似于 `apply` 机制,您需要在该过程的那一点上生成正确形状的代码,然后在后续步骤中看起来像所需的“`apply` 结果”。如上所述,语法引用总是生成创建列表结构的代码,并且特别地,它永远不会返回看起来像未引用的 `do` 或 `if` 等的未引用表达式,因此这是不可能的。
由于给定上述执行模型合理的代码转换可以使用宏实现,因此这不是问题。
顺便说一下,在您的例子中,`macroexpand` 调用实际上是多余的,因为语法引用形式已经与其宏展开相同(因为 `+` 不是宏)。
user=> `(+ ~@'(1 2 3))
(clojure.core/+ 1 2 3)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接