报价单和清单有什么区别?
What is the difference between quote and list?
我知道您可以使用 '
(又名 quote
)来创建列表,我一直使用它,如下所示:
> (car '(1 2 3))
1
但它并不总是像我期望的那样工作。例如,我尝试像这样创建一个函数列表,但没有成功:
> (define math-fns '(+ - * /))
> (map (lambda (fn) (fn 1)) math-fns)
application: not a procedure;
expected a procedure that can be applied to arguments
given: '+
当我使用 list
时,它有效:
> (define math-fns (list + - * /))
> (map (lambda (fn) (fn 1)) math-fns)
'(1 -1 1 1)
为什么?我以为 '
只是一个方便的 shorthand,那么为什么行为不同?
TL;DR: 它们是不同的;有疑问时使用 list
。
一条经验法则:只要你想对参数求值就使用list
; quote
对其参数进行“分配”,因此 '(+ 1 2)
类似于 (list '+ '1 '2)
。您最终会在列表中得到一个符号,而不是一个函数。
in-depth 查看 list
和 quote
在 Scheme 和 Racket 中,quote
和 list
是 完全不同的东西,但是由于它们都可以用来生成列表,所以混淆是常见且可以理解。它们之间有一个非常重要的区别:list
是一个普通的旧 函数 ,而 quote
(即使没有特殊的 '
语法)是一个特殊形式。也就是说,list
可以在plain Scheme中实现,但是quote
不能。
list
函数
list
函数实际上是两者中最简单的一个,所以让我们从这里开始。它是一个接受任意数量参数的函数,并将参数收集到一个列表中。
> (list 1 2 3)
(1 2 3)
上面的例子可能会造成混淆,因为结果打印为 quote
able 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
只是一个函数,所以它可以像任何其他函数一样使用,包括 higher-order 方式。例如,它可以传递给 map
函数,它会正常工作:
> (map list '(1 2 3) '(4 5 6))
((1 4) (2 5) (3 6))
quote
形式
与 list
不同,引用是 Lisp 的一个特殊部分。 quote
形式的特殊部分是因为它有一个特殊的 reader 缩写,'
,但即使没有它,它也是 特殊的。与 list
不同,quote
不是 函数,因此它不需要表现得像一个函数——它有自己的规则。
简单讨论 Lisp 源代码
在Scheme和Racket派生出来的Lisp中,所有的代码其实都是由普通的数据结构组成的。例如,考虑以下表达式:
(+ 1 2)
那个表达式实际上是一个列表,它有三个元素:
+
符号
- 人数
1
- 人数
2
所有这些值都是普通值,可以由程序员创建。创建 1
值真的很容易,因为它会自行计算:您只需键入 1
。但是符号和列表更难:默认情况下,源代码中的符号执行变量查找!即符号不是self-evaluating:
> 1
1
> a
a: undefined
cannot reference undefined identifier
事实证明,符号基本上只是字符串,事实上我们可以在它们之间进行转换:
> (string->symbol "a")
a
列表比符号做的更多,因为默认情况下,源代码中的列表 调用一个函数! 做 (+ 1 2)
查看列表中的第一个元素,+
符号,查找与其关联的函数,并使用列表中的其余元素调用它。
不过,有时您可能希望禁用此“特殊”行为。您可能只想获取列表或获取符号而不对其进行评估。为此,您可以使用 quote
.
引用的含义
考虑到所有这些,quote
的作用就很明显了:它只是“关闭”了它所包装的表达式的特殊求值行为。例如,考虑 quote
ing 一个符号:
> (quote a)
a
类似地,考虑 quote
创建一个列表:
> (quote (a b c))
(a b c)
不管你给什么quote
,它总是,总是吐给你。不多也不少。这意味着如果你给它一个列表,none 的子表达式将被评估——不要期望它们是!如果您需要任何类型的评估,请使用 list
.
现在,有人可能会问:如果 quote
除了符号或列表之外的东西会怎样?好吧,答案是……没什么!你拿回来就行了。
> (quote 1)
1
> (quote "abcd")
"abcd"
这是有道理的,因为 quote
仍然只是吐出你给它的东西。这就是为什么像数字和字符串这样的“文字”在 Lisp 中有时被称为“self-quoting”。
还有一件事:如果你 quote
一个 e包含 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)
报价非常简单,但由于它往往会违背我们对传统评估模型的理解,所以它可能会变得非常复杂。事实上,它令人困惑 因为 它是多么简单:没有特殊情况,没有规则。它只是 returns 正是你给它的,完全按照说明(因此得名“报价单”)。
附录 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)
最后一点:quasiquote 实际上 可以 在 Racket 中使用一个相当复杂的宏来实现,而且确实如此。它扩展到 list
、cons
的用法,当然还有 quote
.
附录 B:在 Scheme
中实现 list
和 quote
实现 list
非常简单,因为“剩余参数”语法的工作原理。这就是您所需要的:
(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 语言菜单中将打印样式更改为“write”。
您看到的行为是 Scheme 未将符号视为函数的结果。
表达式'(+ - * /)
产生一个值,它是一个符号列表。这仅仅是因为 (+ - * /)
是 一个符号列表,我们只是引用它来抑制求值,以便从字面上获取该对象作为值。
表达式(list + - * /)
生成一个函数列表。这是因为它是一个函数调用。计算符号表达式 list
、+
、-
、*
和 /
。它们都是表示函数的变量,因此被简化为那些函数。然后调用 list
函数,returns 一个包含其余四个函数的列表。
在 ANSI Common Lisp 中,调用符号作为函数有效:
[1]> (mapcar (lambda (f) (funcall f 1)) '(+ - * /))
(1 -1 1 1)
当在需要函数的地方使用符号时,符号的 top-level 函数绑定被替换,如果它有一个,一切都很酷。实际上,符号是 Common Lisp 中的 function-callable 个对象。
如果您想使用 list
生成符号列表,就像 '(+ - * /)
一样,您必须单独引用它们以抑制它们的计算:
(list '+ '- '* '/)
回到 Scheme 世界,你会发现如果你 map
超过这个,它会像原来的引用列表一样失败。原因是一样的:试图将符号对象用作函数。
显示给您的错误消息具有误导性:
expected a procedure that can be applied to arguments
given: '+
此处显示的 '+
是 (quote +)
。但这不是申请的内容;它只是 +
,问题是符号对象 +
在该方言中不能用作函数。
这里发生的事情是诊断消息正在以“打印为表达式”模式打印 +
符号,这是 Racket 的一个功能,我猜你正在使用它。
在“打印为表达式”模式下,对象使用必须阅读并评估 的语法来打印以生成类似的对象。请参阅 Whosebug 问题“”
我知道您可以使用 '
(又名 quote
)来创建列表,我一直使用它,如下所示:
> (car '(1 2 3))
1
但它并不总是像我期望的那样工作。例如,我尝试像这样创建一个函数列表,但没有成功:
> (define math-fns '(+ - * /))
> (map (lambda (fn) (fn 1)) math-fns)
application: not a procedure;
expected a procedure that can be applied to arguments
given: '+
当我使用 list
时,它有效:
> (define math-fns (list + - * /))
> (map (lambda (fn) (fn 1)) math-fns)
'(1 -1 1 1)
为什么?我以为 '
只是一个方便的 shorthand,那么为什么行为不同?
TL;DR: 它们是不同的;有疑问时使用 list
。
一条经验法则:只要你想对参数求值就使用list
; quote
对其参数进行“分配”,因此 '(+ 1 2)
类似于 (list '+ '1 '2)
。您最终会在列表中得到一个符号,而不是一个函数。
in-depth 查看 list
和 quote
在 Scheme 和 Racket 中,quote
和 list
是 完全不同的东西,但是由于它们都可以用来生成列表,所以混淆是常见且可以理解。它们之间有一个非常重要的区别:list
是一个普通的旧 函数 ,而 quote
(即使没有特殊的 '
语法)是一个特殊形式。也就是说,list
可以在plain Scheme中实现,但是quote
不能。
list
函数
list
函数实际上是两者中最简单的一个,所以让我们从这里开始。它是一个接受任意数量参数的函数,并将参数收集到一个列表中。
> (list 1 2 3)
(1 2 3)
上面的例子可能会造成混淆,因为结果打印为 quote
able 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
只是一个函数,所以它可以像任何其他函数一样使用,包括 higher-order 方式。例如,它可以传递给 map
函数,它会正常工作:
> (map list '(1 2 3) '(4 5 6))
((1 4) (2 5) (3 6))
quote
形式
与 list
不同,引用是 Lisp 的一个特殊部分。 quote
形式的特殊部分是因为它有一个特殊的 reader 缩写,'
,但即使没有它,它也是 特殊的。与 list
不同,quote
不是 函数,因此它不需要表现得像一个函数——它有自己的规则。
简单讨论 Lisp 源代码
在Scheme和Racket派生出来的Lisp中,所有的代码其实都是由普通的数据结构组成的。例如,考虑以下表达式:
(+ 1 2)
那个表达式实际上是一个列表,它有三个元素:
+
符号- 人数
1
- 人数
2
所有这些值都是普通值,可以由程序员创建。创建 1
值真的很容易,因为它会自行计算:您只需键入 1
。但是符号和列表更难:默认情况下,源代码中的符号执行变量查找!即符号不是self-evaluating:
> 1
1
> a
a: undefined
cannot reference undefined identifier
事实证明,符号基本上只是字符串,事实上我们可以在它们之间进行转换:
> (string->symbol "a")
a
列表比符号做的更多,因为默认情况下,源代码中的列表 调用一个函数! 做 (+ 1 2)
查看列表中的第一个元素,+
符号,查找与其关联的函数,并使用列表中的其余元素调用它。
不过,有时您可能希望禁用此“特殊”行为。您可能只想获取列表或获取符号而不对其进行评估。为此,您可以使用 quote
.
引用的含义
考虑到所有这些,quote
的作用就很明显了:它只是“关闭”了它所包装的表达式的特殊求值行为。例如,考虑 quote
ing 一个符号:
> (quote a)
a
类似地,考虑 quote
创建一个列表:
> (quote (a b c))
(a b c)
不管你给什么quote
,它总是,总是吐给你。不多也不少。这意味着如果你给它一个列表,none 的子表达式将被评估——不要期望它们是!如果您需要任何类型的评估,请使用 list
.
现在,有人可能会问:如果 quote
除了符号或列表之外的东西会怎样?好吧,答案是……没什么!你拿回来就行了。
> (quote 1)
1
> (quote "abcd")
"abcd"
这是有道理的,因为 quote
仍然只是吐出你给它的东西。这就是为什么像数字和字符串这样的“文字”在 Lisp 中有时被称为“self-quoting”。
还有一件事:如果你 quote
一个 e包含 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)
报价非常简单,但由于它往往会违背我们对传统评估模型的理解,所以它可能会变得非常复杂。事实上,它令人困惑 因为 它是多么简单:没有特殊情况,没有规则。它只是 returns 正是你给它的,完全按照说明(因此得名“报价单”)。
附录 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)
最后一点:quasiquote 实际上 可以 在 Racket 中使用一个相当复杂的宏来实现,而且确实如此。它扩展到 list
、cons
的用法,当然还有 quote
.
附录 B:在 Scheme
中实现list
和 quote
实现 list
非常简单,因为“剩余参数”语法的工作原理。这就是您所需要的:
(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 语言菜单中将打印样式更改为“write”。
您看到的行为是 Scheme 未将符号视为函数的结果。
表达式'(+ - * /)
产生一个值,它是一个符号列表。这仅仅是因为 (+ - * /)
是 一个符号列表,我们只是引用它来抑制求值,以便从字面上获取该对象作为值。
表达式(list + - * /)
生成一个函数列表。这是因为它是一个函数调用。计算符号表达式 list
、+
、-
、*
和 /
。它们都是表示函数的变量,因此被简化为那些函数。然后调用 list
函数,returns 一个包含其余四个函数的列表。
在 ANSI Common Lisp 中,调用符号作为函数有效:
[1]> (mapcar (lambda (f) (funcall f 1)) '(+ - * /))
(1 -1 1 1)
当在需要函数的地方使用符号时,符号的 top-level 函数绑定被替换,如果它有一个,一切都很酷。实际上,符号是 Common Lisp 中的 function-callable 个对象。
如果您想使用 list
生成符号列表,就像 '(+ - * /)
一样,您必须单独引用它们以抑制它们的计算:
(list '+ '- '* '/)
回到 Scheme 世界,你会发现如果你 map
超过这个,它会像原来的引用列表一样失败。原因是一样的:试图将符号对象用作函数。
显示给您的错误消息具有误导性:
expected a procedure that can be applied to arguments
given: '+
此处显示的 '+
是 (quote +)
。但这不是申请的内容;它只是 +
,问题是符号对象 +
在该方言中不能用作函数。
这里发生的事情是诊断消息正在以“打印为表达式”模式打印 +
符号,这是 Racket 的一个功能,我猜你正在使用它。
在“打印为表达式”模式下,对象使用必须阅读并评估 的语法来打印以生成类似的对象。请参阅 Whosebug 问题“