为什么可以在准引用列表末尾的非列表上使用非引用拼接?
Why is it possible to use unquote-splicing on a non-list at the end of a quasiquoted list?
准引用列表 `(1 ,@2 3)
无效,因为 2 不是列表。但是,`(1 2 ,@3)
是有效的,并且 return 是一个虚线列表:(1 2 . 3)
。我在 Common Lisp 和 Scheme 中观察到这个结果。为什么可以在准引用列表末尾对非列表使用非引用拼接?为什么结果是点列表?
'(1 2 3)
实际上是 (cons 1 (cons 2 (cons 3 nil)))
.
因此,'(1 2 3)
中的最后一个元素不是'non-list',而是(cons 3 nil)
即(list 3)
,因此可以拼接。
要用准引用构造一个包含 3 个变量的列表,可以这样写
`(,x ,y ,z)
这个可以脱糖到
(cons x (cons y (cons z nil)))
如果 z
是一个列表,我们想将其内容拼接到结果列表中怎么办?然后我们不为它创建一个新的 cons,而是简单地将它放在尾部。当我们写
`(,x ,y ,@z)
这个脱糖到
(cons x (cons y z))
如果 z
碰巧不是一个列表,那么结果是一个合法值,尽管不是一个正确的列表。例如,如您所见,(1 2 . 3)
。如果您显式编写脱糖形式,您将看到相同的结果,如 (cons 1 (cons 2 3))
。如果这也困扰您,您可能需要研究一般不正确列表或点对符号的概念。
表达式 `(1 2 ,@3)
在 Scheme 或 Common Lisp 中均无效。
方案
在 R6RS 方案中(对于 R5RS 也是类似的),未指定使用 unquote-splicing
在非列表上操作的行为。 R6RS 方案标准要求 (11.17 Quasiquotation):
If an (unquote-splicing <expression> ...) form appears inside a <qq
template>, then the <expression>s must evaluate to lists....
普通 Lisp
Common Lisp HyperSpec 首先说 (2.4.6 Backquote):
If a comma is immediately followed by an at-sign, then the form
following the at-sign is evaluated to produce a list of objects.
These objects are then "spliced" into place in the template.
在子表达式 ,@3
中,3
不计算为列表。这似乎是一个非常有力的论据,即表达式无效。即使 3
在拼接之前被神奇地放在一个列表中,这也不会导致点列表。 HyperSpec 继续提供反引号语法的正式总结。感兴趣的部分是:
`(x1 x2 x3 ... xn . atom) may be interpreted to mean
(append [ x1] [ x2] [ x3] ... [ xn] (quote atom))
where the brackets are used to indicate a transformation of an xj as follows:
-- [form] is interpreted as (list `form), which contains a backquoted form that must then be further interpreted.
-- [,form] is interpreted as (list form).
-- [,@form] is interpreted as 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 报告的相同行为。
一个有趣的案例
我还针对 an implementation of the backquote macro by Guy Steele and published in 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 槽。
一些函数只有在给定适当的列表时才能工作,其他函数没有这样的限制,通常是因为检查这个适当的ty 不是没有成本的。
写作 (cons 1 2)
创建这样一个列表。现在,如果拼接语法扩展为 (cons x y)
,其中 y
是一个非列表,您也会有一个虚线列表。
切片
第 2.4.6 Backquote 章指出(强调我的):
If a comma is immediately followed by an at-sign, then the form following the at-sign is evaluated to produce a list of objects.
我没有看到其他注释表明拼接非列表值可能是 conforming program 的一部分,即使当拼接发生在列表末尾时它实际上会导致不正确的列表。
该部分还指出:
`((,a b) ,c ,@d)
will be interpreted as if it were
(append (list (append (list a) (list 'b) 'nil)) (list c) d 'nil)
but it could also be legitimately interpreted to mean any of the following:
(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
等标准函数的调用使极端情况自然产生不正确的列表,但请注意第一个和最后一个示例 not 允许拼接最后一个元素。例如:
(list* (cons a '(b)) c (copy-list d))
当 d
不是列表时,上面的表格会报错,因为 COPY-LIST
只适用于列表(正确的或带点的,但在这里它会被赋予一个非列表值,例如 a数)。
因此我的理解是这个表达式:
`(1 2 ,@3)
实际上在 Common Lisp 中是无效的,碰巧只是偶然地起作用。编写它可能会使您面临可移植性问题。
结论
Why is it possible to use unquote-splicing for non-lists at the end of a quasiquoted list?
它在您的实现中是偶然发生的,因为反引号、反引号和拼接 rewritten/expanded 作为列表构建函数可能会在极端情况下生成点列表,而不会发出错误信号。
Why is the result a dotted list?
因为代码可能被解释为对 append
的调用,其中最后一项是非列表值,这导致点列表。它在表格中间拼接时不起作用,因为 append
期望除最后一个参数之外的所有参数的正确列表。
奖金
今天我了解到 ,.
类似于 ,@
除了可以使用 nconc
而不是 append
准引用列表 `(1 ,@2 3)
无效,因为 2 不是列表。但是,`(1 2 ,@3)
是有效的,并且 return 是一个虚线列表:(1 2 . 3)
。我在 Common Lisp 和 Scheme 中观察到这个结果。为什么可以在准引用列表末尾对非列表使用非引用拼接?为什么结果是点列表?
'(1 2 3)
实际上是 (cons 1 (cons 2 (cons 3 nil)))
.
因此,'(1 2 3)
中的最后一个元素不是'non-list',而是(cons 3 nil)
即(list 3)
,因此可以拼接。
要用准引用构造一个包含 3 个变量的列表,可以这样写
`(,x ,y ,z)
这个可以脱糖到
(cons x (cons y (cons z nil)))
如果 z
是一个列表,我们想将其内容拼接到结果列表中怎么办?然后我们不为它创建一个新的 cons,而是简单地将它放在尾部。当我们写
`(,x ,y ,@z)
这个脱糖到
(cons x (cons y z))
如果 z
碰巧不是一个列表,那么结果是一个合法值,尽管不是一个正确的列表。例如,如您所见,(1 2 . 3)
。如果您显式编写脱糖形式,您将看到相同的结果,如 (cons 1 (cons 2 3))
。如果这也困扰您,您可能需要研究一般不正确列表或点对符号的概念。
表达式 `(1 2 ,@3)
在 Scheme 或 Common Lisp 中均无效。
方案
在 R6RS 方案中(对于 R5RS 也是类似的),未指定使用 unquote-splicing
在非列表上操作的行为。 R6RS 方案标准要求 (11.17 Quasiquotation):
If an (unquote-splicing <expression> ...) form appears inside a <qq template>, then the <expression>s must evaluate to lists....
普通 Lisp
Common Lisp HyperSpec 首先说 (2.4.6 Backquote):
If a comma is immediately followed by an at-sign, then the form following the at-sign is evaluated to produce a list of objects. These objects are then "spliced" into place in the template.
在子表达式 ,@3
中,3
不计算为列表。这似乎是一个非常有力的论据,即表达式无效。即使 3
在拼接之前被神奇地放在一个列表中,这也不会导致点列表。 HyperSpec 继续提供反引号语法的正式总结。感兴趣的部分是:
`(x1 x2 x3 ... xn . atom) may be interpreted to mean
(append [ x1] [ x2] [ x3] ... [ xn] (quote atom))
where the brackets are used to indicate a transformation of an xj as follows:
-- [form] is interpreted as (list `form), which contains a backquoted form that must then be further interpreted.
-- [,form] is interpreted as (list form).
-- [,@form] is interpreted as 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 报告的相同行为。
一个有趣的案例
我还针对 an implementation of the backquote macro by Guy Steele and published in 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 槽。
一些函数只有在给定适当的列表时才能工作,其他函数没有这样的限制,通常是因为检查这个适当的ty 不是没有成本的。
写作 (cons 1 2)
创建这样一个列表。现在,如果拼接语法扩展为 (cons x y)
,其中 y
是一个非列表,您也会有一个虚线列表。
切片
第 2.4.6 Backquote 章指出(强调我的):
If a comma is immediately followed by an at-sign, then the form following the at-sign is evaluated to produce a list of objects.
我没有看到其他注释表明拼接非列表值可能是 conforming program 的一部分,即使当拼接发生在列表末尾时它实际上会导致不正确的列表。
该部分还指出:
`((,a b) ,c ,@d)
will be interpreted as if it were
(append (list (append (list a) (list 'b) 'nil)) (list c) d 'nil)
but it could also be legitimately interpreted to mean any of the following:
(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
等标准函数的调用使极端情况自然产生不正确的列表,但请注意第一个和最后一个示例 not 允许拼接最后一个元素。例如:
(list* (cons a '(b)) c (copy-list d))
当 d
不是列表时,上面的表格会报错,因为 COPY-LIST
只适用于列表(正确的或带点的,但在这里它会被赋予一个非列表值,例如 a数)。
因此我的理解是这个表达式:
`(1 2 ,@3)
实际上在 Common Lisp 中是无效的,碰巧只是偶然地起作用。编写它可能会使您面临可移植性问题。
结论
Why is it possible to use unquote-splicing for non-lists at the end of a quasiquoted list?
它在您的实现中是偶然发生的,因为反引号、反引号和拼接 rewritten/expanded 作为列表构建函数可能会在极端情况下生成点列表,而不会发出错误信号。
Why is the result a dotted list?
因为代码可能被解释为对 append
的调用,其中最后一项是非列表值,这导致点列表。它在表格中间拼接时不起作用,因为 append
期望除最后一个参数之外的所有参数的正确列表。
奖金
今天我了解到 ,.
类似于 ,@
除了可以使用 nconc
而不是 append