4

quasiquoted 列表`(1 ,@2 3)无效,因为 2 不是列表。但是,`(1 2 ,@3)它是有效的并且会返回一个虚线列表:(1 2 . 3)。我在 Common Lisp 和 Scheme 中观察到了这个结果。为什么可以在 quasiquoted 列表的末尾对非列表使用 unquote-splicing?为什么结果是点列表?

4

4 回答 4

7

该表达式`(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中,@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 中显示的内容,要么以其他方式扩展反引号形式,看起来非列表形式可以遵循,@。不要依赖这个,因为很难说它什么时候不起作用。

于 2021-01-19T22:33:17.850 回答
5

适当的清单

其他答案已经详细说明了这部分,让我快速改写一下:列表是正确的列表或不正确的列表;不正确的列表是循环列表或点列表。

特别是,点列表是一个非循环列表,其最后一个 cons 单元格具有非列表cdr槽。

某些功能仅在给定适当的列表时才能工作,而其他功能则没有此类限制,通常是因为检查此属性并非没有成本。写作(cons 1 2)创建了这样一个列表。现在,如果拼接语法扩展为(cons x y)where yis 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 dis not a list,因为COPY-LIST只适用于列表(正确的或点的,但在这里它会被赋予一个非列表值,例如一个数字)。

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

`(1 2 ,@3)

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

结论

为什么可以在 quasiquoted 列表的末尾对非列表使用 unquote-splicing?

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

为什么结果是点列表?

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

奖金

今天我了解了,.which is like ,@except is may use nconcinstead ofappend

于 2021-01-19T23:34:59.640 回答
2

要使用 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)). 如果这也让您感到困扰,您可能需要调查一般不正确列表的概念,或点对符号。

于 2021-01-19T21:02:39.207 回答
-1

'(1 2 3)实际上是(cons 1 (cons 2 (cons 3 nil)))

因此,最后一个元素'(1 2 3)不是“非列表”,而是一个(cons 3 nil)ie (list 3),因此可以拼接。

于 2021-01-19T19:54:09.300 回答