quasiquoted 列表`(1 ,@2 3)
无效,因为 2 不是列表。但是,`(1 2 ,@3)
它是有效的并且会返回一个虚线列表:(1 2 . 3)
。我在 Common Lisp 和 Scheme 中观察到了这个结果。为什么可以在 quasiquoted 列表的末尾对非列表使用 unquote-splicing?为什么结果是点列表?
4 回答
该表达式`(1 2 ,@3)
在 Scheme 或 Common Lisp 中均无效。
方案
在 R6RS 方案中(对于 R5RS 也是如此),未指定行为以在具有unquote-splicing
. R6RS 方案标准要求(11.17 Quasiquotation):
如果 (unquote-splicing <expression> ...) 形式出现在 <qq template> 中,则 <expression> 必须计算为列表 ....
通用 Lisp
Common Lisp HyperSpec 首先说(2.4.6 Backquote):
如果逗号后面紧跟一个at 符号,则评估at 符号 后面的形式以生成对象列表。然后将这些对象“拼接”到模板中的适当位置。
在 sub-expression中,@3
,3
不计算为列表。这似乎是一个非常有力的论据,即表达式无效。即使3
在拼接之前被神奇地放在一个列表中,这也不会导致一个点列表。HyperSpec 继续提供反引号语法的正式摘要。感兴趣的部分是:
`(x1 x2 x3 ... xn . atom) 可以解释为
(追加 [ x1] [ x2] [ x3] ... [ xn](引用原子))
其中括号用于表示 xj 的变换,如下所示:
-- [form] 被解释为 (list `form),它包含一个反引号的形式,然后必须进一步解释。
-- [,form] 被解释为(列表形式)。
-- [,@form] 被解释为表单。
所以在 Common Lisp 中,原来的表达式,相当于`(1 2 ,@3 . nil)
,可以解释为:
(append (list `1) (list `2) 3 (quote nil))
但是,这不是对 的有效调用append
,它需要为除最后一个参数之外的所有参数提供适当的列表。因此,似乎不支持原始表达式有效的想法。
它在 Scheme 和 Common Lisp 中都适用于 OP 的事实可能归结为跨不同实现的反引号宏的类似定义。这些定义似乎都期望下面的形式,@
将评估为一个列表;根据标准,如果不是这种情况,则不能依赖观察到的行为(生成点列表)。也就是说,我测试了 Chez Scheme、Guile Scheme、MIT Scheme、SBCL、CCL 和 CLisp:它们都表现出 OP 报告的相同行为。
一个有趣的案例
我还针对Guy Steele 的反引号宏实现进行了测试,并在 CLTL2 中发布。这个案例更有趣。CLTL2 中的这种反引号实现旨在探索反引号表达式的行为,并具有可选的代码简化阶段。这里$
对应一个反引号,%@
对应,@
. 不做代码简化,将原表达式展开的结果是:
CL-USER> (setf *bq-simplify* nil)
NIL
CL-USER> (try '("$(1 2 %@3)"))
`(1 2 ,@3) = (APPEND (LIST '1) (LIST '2) 3 'NIL)
这对应于上面通过阅读 HyperSpec 中的描述所期望的表达式。但请注意,此表达式不会编译:
CL-USER> (append (list 1) (list 2) 3 nil)
The value
3
is not of type
LIST
[Condition of type TYPE-ERROR]
但是,当启用代码简化时:
CL-USER> (setf *bq-simplify* t)
T
CL-USER> (try '("$(1 2 %@3)"))
`(1 2 ,@3) = (LIST* '1 '2 3)
这个“简化”的表达式是有效的,并计算为一个点列表:
CL-USER> (list* 1 2 3)
(1 2 . 3)
我的结论是,下面的表达式,@
必须是一个符合 Common Lisp 标准的列表,但是一些常见的实现要么进行某种形式的代码简化,类似于 CLTL2 中显示的内容,要么以其他方式扩展反引号形式,看起来非列表形式可以遵循,@
。不要依赖这个,因为很难说它什么时候不起作用。
适当的清单
其他答案已经详细说明了这部分,让我快速改写一下:列表是正确的列表或不正确的列表;不正确的列表是循环列表或点列表。
特别是,点列表是一个非循环列表,其最后一个 cons 单元格具有非列表cdr槽。
某些功能仅在给定适当的列表时才能工作,而其他功能则没有此类限制,通常是因为检查此属性并非没有成本。写作(cons 1 2)
创建了这样一个列表。现在,如果拼接语法扩展为(cons x y)
where y
is a non-list,你也会有一个点列表。
切片
第2.4.6章反引号状态(强调我的):
如果逗号后面紧跟一个 at 符号,则评估at 符号后面的形式以生成对象列表。
我没有看到其他说明表明拼接非列表值可能是符合程序的一部分,即使当拼接发生在列表末尾时它实际上会导致不正确的列表。
该部分还指出:
`((,a b) ,c ,@d)
将被解释为
(append (list (append (list a) (list 'b) 'nil)) (list c) d 'nil)
但它也可以合法地解释为以下任何一种:
(append (list (append (list a) (list 'b))) (list c) d) (append (list (append (list a) '(b))) (list c) d) (list* (cons a '(b)) c d) (list* (cons a (list 'b)) c d) (append (list (cons a '(b))) (list c) d) (list* (cons a '(b)) c (copy-list d))
扩展为对标准函数的调用list*
或append
使该极端情况自然产生不正确的列表,但请注意第一个和最后一个示例不允许拼接最后一个元素。例如:
(list* (cons a '(b)) c (copy-list d))
上面的表格抱怨 when d
is not a list,因为COPY-LIST
只适用于列表(正确的或点的,但在这里它会被赋予一个非列表值,例如一个数字)。
因此,我的理解是这个表达式:
`(1 2 ,@3)
实际上在 Common Lisp 中是无效的,并且只是偶然地起作用。编写它可能会使您面临可移植性问题。
结论
为什么可以在 quasiquoted 列表的末尾对非列表使用 unquote-splicing?
它在您的实现中偶然发生,因为反引号、取消引号和拼接被重写/扩展为列表构建函数,在这种极端情况下可能会生成点列表,而不会发出错误信号。
为什么结果是点列表?
因为代码可能被解释为append
对最后一项是非列表值的调用,这会导致一个点列表。在表单中间拼接时它不起作用,因为append
除了最后一个参数之外的所有参数都需要正确的列表。
奖金
今天我了解了,.
which is like ,@
except is may use nconc
instead ofappend
要使用 quasiquoting 构造一个包含 3 个变量的列表,可以编写
`(,x ,y ,z)
这可以去糖
(cons x (cons y (cons z nil)))
如果z
是一个列表,我们想将它的内容拼接到结果列表中怎么办?然后,我们没有为它创建一个新的缺点,而是简单地将它放在尾部。当我们写
`(,x ,y ,@z)
这对
(cons x (cons y z))
如果z
碰巧实际上不是列表,则结果是合法值,尽管不是正确的列表。例如(1 2 . 3)
,正如您所观察到的。如果您明确编写脱糖形式,您将看到相同的结果,如(cons 1 (cons 2 3))
. 如果这也让您感到困扰,您可能需要调查一般不正确列表的概念,或点对符号。
'(1 2 3)
实际上是(cons 1 (cons 2 (cons 3 nil)))
。
因此,最后一个元素'(1 2 3)
不是“非列表”,而是一个(cons 3 nil)
ie (list 3)
,因此可以拼接。