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)
上面的示例可能会让人感到困惑,因为结果是以可以引用
的s表达式形式打印出来的,而在这种情况下,两种语法是等价的。但是如果我们稍微复杂一些,你会发现它是不同的:
> (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
表单
引用(Quotation)是Lisps中的一种特殊形式,不同于list
。 quote
表单很特殊,部分原因是它获取了一个特殊的读取器缩写('
),但即使没有这个缩写也是很特殊的。与list
不同,quote
不是一个函数,因此它不需要像函数一样运行——它有自己的规则。
Lisp源代码的简要讨论
在Lisp中(包括Scheme和Racket),所有代码实际上都由普通数据结构组成。例如,请考虑以下表达式:
(+ 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 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:准引言
因此,如果引言完全禁用了评估,那么它有何用处呢?除了制作提前知道的字符串、符号或数字列表之外,没有太多用处。幸运的是,准引言的概念提供了一种打破引言并返回普通评估的方法。
基础知识非常简单:不要使用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实际上也可以实现quasiquote。它会扩展成对list
、cons
和当然是quote
的使用。
附录B:在Scheme中实现list
和quote
由于“剩余参数”语法的工作方式,实现list
非常简单。这是你需要的全部内容:
(define (list . args)
args)
就是这样!
相比之下,quote
则更加困难,事实上它是不可能的!尽管禁用表达式求值的想法听起来很像宏,但一个天真的尝试会揭示出问题:
(define fake-quote
(syntax-rules ()
((_ arg) arg)))
我们只是获取
arg
并将其拆分,但这样做行不通。为什么呢?因为我们的宏的结果将被评估,所以一切都是徒劳。我们可能能够扩展到类似于
quote
的东西,通过扩展到
(list ...)
并递归地引用元素,像这样:
(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 打印约定
在 Racket 中运行本答案中的示例时,您可能会发现它们的输出结果与预期不同。通常情况下,它们可能会以前导'
打印,例如这个例子:
> (list 1 2 3)
'(1 2 3)
这是因为Racket默认情况下会尽可能地将结果打印为表达式。也就是说,您应该能够在REPL中输入结果并获得相同的值。我个人认为这种行为很好,但在试图理解引用时可能会令人困惑,因此如果您想关闭它,请调用(print-as-expression #f)
,或在DrRacket语言菜单中更改打印样式为“write”。
(define math-fns (map (lambda (s) (lambda args (eval (s . args) (environment '(rnrs))))) '(+ - * /)))
。 - bipllset-car!
或set-cdr!
操作。 - mihai