TL;DR:它们是不同的;有疑问时使用list。
经验法则:只要你想评估参数,就使用list; quote“分布”在它的参数上,所以'(+ 1 2)就像(list '+ '1 '2)。你最终会在你的列表中得到一个符号,而不是一个函数。
深入了解list 和quote
在 Scheme 和 Racket 中,quote 和 list 是完全不同的东西,但由于它们都可以用来生成列表,因此混淆是常见且可以理解的。它们之间有一个非常重要的区别:list 是一个普通的旧函数,而quote(即使没有特殊的' 语法)是一个特殊形式 .即list可以用普通Scheme实现,而quote不能。
list 函数
list 函数实际上是两者中最简单的,所以让我们从这里开始。它是一个接受任意数量参数的函数,并将参数收集到一个列表中。
> (list 1 2 3)
(1 2 3)
上面的例子可能会让人感到困惑,因为结果被打印为quoteable s-expression,这是真的,在这种情况下,两种语法是等价的。但是如果我们稍微复杂一点,你会发现它是不同的:
> (list 1 (+ 1 1) (+ 1 1 1))
(1 2 3)
> '(1 (+ 1 1) (+ 1 1 1))
(1 (+ 1 1) (+ 1 1 1))
quote 示例中发生了什么?好吧,我们稍后会讨论这个问题,但首先,看看list。它只是一个普通的函数,所以它遵循标准的 Scheme 求值语义:它在传递给函数之前对它的每个参数求值。这意味着像(+ 1 1) 这样的表达式在被收集到列表之前将被简化为2。
当向列表函数提供变量时,这种行为也可见:
> (define x 42)
> (list x)
(42)
> '(x)
(x)
使用list,x 在传递给list 之前得到评估。有了quote,事情就变得更复杂了。
最后,因为list 只是一个函数,它可以像任何其他函数一样使用,包括高阶方式。例如,它可以传递给map 函数,它会正常工作:
> (map list '(1 2 3) '(4 5 6))
((1 4) (2 5) (3 6))
quote 表单
与list 不同,引号是 Lisps 的一个特殊部分。 quote 形式的特殊部分是因为它有一个特殊的读者缩写',但即使没有它,它也也特别。与list 不同,quote不是一个函数,因此它不需要表现得像一个函数——它有自己的规则。
浅谈Lisp源码
在Scheme和Racket衍生的Lisp中,所有的代码实际上都是由普通的数据结构组成的。例如,考虑以下表达式:
(+ 1 2)
那个表达式实际上是一个列表,它包含三个元素:
所有这些值都是程序员可以创建的正常值。创建1 值真的很容易,因为它会评估自己:您只需输入1。但是符号和列表更难:默认情况下,源代码中的符号会进行变量查找!也就是说,符号不是自我评估:
> 1
1
> a
a: undefined
cannot reference undefined identifier
但事实证明,符号基本上只是字符串,实际上我们可以在它们之间进行转换:
> (string->symbol "a")
a
列表比符号做的更多,因为默认情况下,源代码中的列表调用一个函数! 执行(+ 1 2) 会查看列表中的第一个元素,+ 符号,查找与其关联的函数,并使用列表中的其余元素调用它。
不过,有时您可能希望禁用这种“特殊”行为。您可能只想获取列表或获取符号而不对其进行评估。为此,您可以使用quote。
引用的含义
考虑到这一切,很明显quote 做了什么:它只是“关闭”了它包装的表达式的特殊评估行为。例如,考虑quote一个符号:
> (quote a)
a
同样,考虑quote一个列表:
> (quote (a b c))
(a b c)
无论你给quote 什么,它都会,总是 向你吐回。不多也不少。这意味着如果你给它一个列表,则不会计算任何子表达式——不要期望它们会被计算!如果您需要任何类型的评估,请使用list。
现在,有人可能会问:如果您quote 不是符号或列表,会发生什么?嗯,答案是……什么都没有!你把它拿回来。
> (quote 1)
1
> (quote "abcd")
"abcd"
这是有道理的,因为quote 仍然只是准确地吐出你给它的东西。这就是为什么像数字和字符串这样的“文字”有时在 Lisp 用语中被称为“自引用”。
还有一件事:如果你quote 一个包含quote 的表达式会发生什么?也就是说,如果你“加倍quote”呢?
> (quote (quote 3))
'3
那里发生了什么?好吧,请记住' 实际上只是quote 的直接缩写,所以根本没有发生什么特别的事情!事实上,如果你的 Scheme 有办法在打印时禁用缩写,它会如下所示:
> (quote (quote 3))
(quote 3)
不要被quote 的特殊性所迷惑:就像(quote (+ 1)),这里的结果只是一个普通的旧列表。事实上,我们可以从列表中取出第一个元素:你能猜出它是什么吗?
> (car (quote (quote 3)))
quote
如果你猜到了3,那你就错了。请记住,quote 禁用所有评估,包含quote 符号的表达式仍然只是一个普通列表。在 REPL 中使用它,直到你对它感到满意为止。
> (quote (quote (quote 3)))
''3
(quote (1 2 (quote 3)))
(1 2 '3)
报价非常简单,但它可能会变得非常复杂,因为它往往会违背我们对传统评估模型的理解。事实上,它令人困惑因为它是多么简单:没有特殊情况,没有规则。它只是准确地返回你给它的东西,正如所说的(因此得名“引用”)。
附录 A:准引用
如果引用完全禁用评估,它有什么用?好吧,除了列出提前知道的字符串、符号或数字之外,不多。幸运的是,quasiquotation 的概念提供了一种打破引用并回到普通评估的方法。
基础非常简单:不要使用quote,而是使用quasiquote。通常,这在各个方面都与quote 完全一样:
> (quasiquote 3)
3
> (quasiquote x)
x
> (quasiquote ((a b) (c d)))
((a b) (c d))
quasiquote 的特别之处在于它可以识别一个特殊符号 unquote。无论unquote 出现在列表中的哪个位置,它都会被它包含的任意表达式替换:
> (quasiquote (1 2 (+ 1 2)))
(1 2 (+ 1 2))
> (quasiquote (1 2 (unquote (+ 1 2))))
(1 2 3)
这使您可以使用quasiquote 来构建具有“漏洞”的模板,这些“漏洞”需要用unquote 填充。这意味着实际上可以在引用列表中包含变量的值:
> (define x 42)
> (quasiquote (x is: (unquote x)))
(x is: 42)
当然,使用quasiquote 和unquote 相当冗长,所以它们有自己的缩写,就像'。具体来说,quasiquote 是 `(反引号),unquote 是 ,(逗号)。有了这些缩写,上面的例子就更可口了。
> `(x is: ,x)
(x is: 42)
最后一点:准引用实际上可以在 Racket 中使用一个相当复杂的宏来实现,而且确实如此。它扩展到list、cons,当然还有quote。
附录 B:在 Scheme 中实现 list 和 quote
实现list 非常简单,因为“rest argument”语法的工作原理。这就是你所需要的:
(define (list . args)
args)
就是这样!
相比之下,quote 要难得多——事实上,这是不可能的!这似乎完全可行,因为禁用评估的想法听起来很像宏。然而,一个天真的尝试揭示了问题:
(define fake-quote
(syntax-rules ()
((_ arg) arg)))
我们只是把arg 吐出来......但这不起作用。为什么不?好吧,我们宏的结果将被评估,所以一切都是徒劳的。我们可以通过扩展为(list ...) 并递归地引用元素来扩展为quote 之类的东西,如下所示:
(define impostor-quote
(syntax-rules ()
((_ (a . b)) (cons (impostor-quote a) (impostor-quote b)))
((_ (e ...)) (list (impostor-quote e) ...))
((_ x) x)))
不幸的是,如果没有过程宏,我们无法处理没有quote 的符号。我们可以使用syntax-case 更接近,但即便如此,我们也只能模仿quote 的行为,而不是复制它。
附录 C:球拍打印约定
在 Racket 中尝试此答案中的示例时,您可能会发现它们没有按预期打印。通常,他们可能会以' 开头,例如在此示例中:
> (list 1 2 3)
'(1 2 3)
这是因为默认情况下,Racket 会尽可能将结果打印为表达式。也就是说,您应该能够将结果键入 REPL 并返回相同的值。我个人觉得这种行为很好,但在尝试理解引用时可能会令人困惑,所以如果你想关闭它,请致电(print-as-expression #f),或在 DrRacket 语言菜单中将打印样式更改为“写”。