Clojure,在语法引用之外取消引用切片

Tuo*_*nen 2 lisp macros functional-programming clojure

在clojure中,我们可以使用非引用切片~@来传播列表.例如

(macroexpand `(+ ~@'(1 2 3))) 
Run Code Online (Sandbox Code Playgroud)

扩展到

(clojure.core/+ 1 2 3)
Run Code Online (Sandbox Code Playgroud)

重新排列语法时,这在宏中很有用.但是,它可以使用所享有切片或熟悉技术的宏之外,没有 eval

这是解决方案 eval

(eval `(+ ~@'(1 2 3))) ;-> 6
Run Code Online (Sandbox Code Playgroud)

但我宁愿这样做

(+ ~@'(1 2 3))
Run Code Online (Sandbox Code Playgroud)

不幸的是,这会引发错误

IllegalStateException Attempting to call unbound fn: #'clojure.core/unquote-splicing  clojure.lang.Var$Unbound.throwArity (Var.java:43)
Run Code Online (Sandbox Code Playgroud)

起初我以为apply会这样做,事实确实如此

(apply + '(1 2 3)) ; -> 6
Run Code Online (Sandbox Code Playgroud)

但是,宏或特殊形式不是这种情况.宏显而易见,因为它在应用之前已经扩展,并且无论如何必须是表单中的第一个元素.虽然特殊形式不是那么明显,但仍然有意义,因为它们不像功能一样是一等公民.例如,以下引发错误

(apply do ['(println "hello") '(println "world")]) ;-> error
Run Code Online (Sandbox Code Playgroud)

是否是在运行时"将"列表"应用"到特殊表单以使用非引号切片的唯一方法eval

Mic*_*zyk 7

Clojure有一个简单的模型,用于加载和执行程序.略有简化,它是这样的:

  1. 读者从文本流中读取一些源代码;

  2. 这一次一次传递给编译器;

  3. 编译器扩展它遇到的任何宏;

  4. 对于非宏,编译器应用各种简单的评估规则(特殊形式的特殊规则,文字评估自己,函数调用如此编译等);

  5. 编译已编译的代码,并可能更改以下表单使用的编译环境.

语法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))))
Run Code Online (Sandbox Code Playgroud)

这是只有在语法引用块的背景下,读者得到~~@这个特殊的处理,语法援引块总是产生可能调用的序列建设职能一把从形式clojure.core和引用的数据,否则组成.

这一切都发生在上面列表中的第1步的一部分.因此,对于作为类似apply机制的语法引用,您需要它在流程中的那一点生成正确形状的代码,然后apply在后续步骤中看起来像所需的" 结果".如上所述,syntax-quote总是生成创建列表结构的代码,特别是它永远不会返回看起来像不带引号的dos或ifs等的不带引号的表达式,所以这是不可能的.

这不是问题,因为给定上述执行模型的合理的代码转换可以使用宏来实现.

顺便说一句,macroexpand电话是你的榜样其实是多余的,因为语法引号形式已一样的宏扩展(理所应当的,因为+不是宏):

user=> `(+ ~@'(1 2 3))
(clojure.core/+ 1 2 3)
Run Code Online (Sandbox Code Playgroud)