为什么可以在准引用列表末尾的非列表上使用取消引用拼接?

quasiquoted 列表`(1 ,@2 3)无效,因为 2 不是列表。但是,`(1 2 ,@3)是有效的,将返回一个点列表:(1 2 . 3)。我在 Common Lisp 和 Scheme 中观察到了这个结果。为什么可以在准引用列表的末尾对非列表使用取消引用拼接?为什么结果是点线表?

回答

该表达式`(1 2 ,@3)在 Scheme 或 Common Lisp 中均无效。


方案

在 R6RS 方案中(对于 R5RS 也是如此),没有指定在非列表上操作的行为unquote-splicing。R6RS 方案标准要求(11.17 准报价):

如果 (unquote-splicing <expression> ...) 形式出现在 <qq template> 中,则 <expression> 必须计算为列表...。


通用 Lisp

Common Lisp HyperSpec 首先说(2.4.6 反引号):

如果一个逗号之后紧接着一个at符号,则该形式
以下at符号被评估以产生一个列表对象。然后将这些对象“拼接”到模板中。

在子表达式中,@33不计算为列表。这似乎是一个非常有力的论据,即该表达式无效。即使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槽。

有些功能预计工作时才给予适当的名单,其他都没有这样的限制,通常是因为检查此适当TY并非没有成本。写作(cons 1 2)创建了这样一个列表。现在,如果拼接语法扩展为(cons x y)wherey是一个非列表,你也会有一个点列表。

切片

第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))

上面的表单抱怨 whend不是列表,因为COPY-LIST只适用于列表(正确的或带点的,但在这里它会被赋予一个非列表值,例如一个数字)。

因此,我的理解是这个表达:

`(1 2 ,@3)

实际上在 Common Lisp 中是无效的,并且碰巧只是偶然起作用。编写它可能会使您面临可移植性问题。

结论

为什么可以在准引用列表的末尾对非列表使用取消引用拼接?

它在您的实现中偶然发生,因为反引号、取消引号和拼接被重写/扩展为列表构建函数,在这种情况下可能会生成点列表,而不会发出错误信号。

为什么结果是点线表?

因为代码可能被解释为append对最后一项是非列表值的调用,这会导致点列表。在表单中间拼接时它不起作用,因为append除了最后一个参数之外的所有参数都需要正确的列表。

奖金

今天笔者了解到,.这就像,@不同的是可以使用nconc的,而不是append


以上是为什么可以在准引用列表末尾的非列表上使用取消引用拼接?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>