引号和列表有什么区别?

Ale*_*ing 33 evaluation scheme racket quote

我知道你可以使用'(aka quote)创建一个列表,我一直都在使用它,如下所示:

> (car '(1 2 3))
1
Run Code Online (Sandbox Code Playgroud)

但它并不总是像我期望的那样工作.例如,我试图创建一个函数列表,像这样,但它不起作用:

> (define math-fns '(+ - * /))
> (map (lambda (fn) (fn 1)) math-fns)
application: not a procedure;
  expected a procedure that can be applied to arguments
  given: '+
Run Code Online (Sandbox Code Playgroud)

当我使用时list,它的工作原理:

> (define math-fns (list + - * /))
> (map (lambda (fn) (fn 1)) math-fns)
'(1 -1 1 1)
Run Code Online (Sandbox Code Playgroud)

为什么?我认为'这只是一个方便的速记,为什么行为不同?

Ale*_*ing 72

TL; DR:他们是不同的; list有疑问时使用

经验法则:list每当您想要评估参数时使用; quote"分配"其论点,所以'(+ 1 2)就像(list '+ '1 '2).您最终会在列表中使用符号,而不是函数.


深入了解listquote

在流程和球拍,quote并且list完全不同的事情,但由于它们的可用于生产单,混乱是常见的,可以理解的.它们之间有一个非常重要的区别:list是一个普通的旧函数,而quote(即使没有特殊的'语法)是一种特殊的形式.也就是说,list可以在普通的Scheme中实现,但quote不能.

list功能

list到目前为止,这个功能实际上只是两者中的简单,所以让我们从那里开始.它是一个接受任意数量参数的函数,它将参数收集到列表中.

> (list 1 2 3)
(1 2 3)
Run Code Online (Sandbox Code Playgroud)

上面的例子可能会quote让人感到困惑,因为结果被打印为一个能够表达式,而且在这种情况下,两个语法是等价的.但如果我们稍微复杂一些,你会发现它有所不同:

> (list 1 (+ 1 1) (+ 1 1 1))
(1 2 3)
> '(1 (+ 1 1) (+ 1 1 1))
(1 (+ 1 1) (+ 1 1 1))
Run Code Online (Sandbox Code Playgroud)

这个quote例子中发生了什么?好吧,我们稍后会讨论这个问题,但首先,请看一下list.它只是一个普通的函数,因此它遵循标准的Scheme评估语义:它传递给函数之前计算每个参数.这意味着类似的表达式(+ 1 1)将被2收集到列表之前.

向列表函数提供变量时,此行为也可见:

> (define x 42)
> (list x)
(42)
> '(x)
(x)
Run Code Online (Sandbox Code Playgroud)

使用list,在x传递之前评估获得list.随着quote,事情变得更加复杂.

最后,因为list它只是一个函数,它可以像任何其他函数一样使用,包括以更高阶的方式.例如,它可以传递给map函数,它将正常工作:

> (map list '(1 2 3) '(4 5 6))
((1 4) (2 5) (3 6))
Run Code Online (Sandbox Code Playgroud)

quote表格

与此不同list,引用是Lisps的一个特殊部分.该quote形式在部分特殊的,因为它得到专门的阅读器的简称,'但它也是特殊的,即使没有这一点.不同于list,quote不是一个函数,因此它不需要像一个函数 - 它有自己的规则.

Lisp源代码的简要讨论

在Lisp中,Scheme和Racket是衍生品,所有代码实际上都是由普通的数据结构组成.例如,请考虑以下表达式:

(+ 1 2)
Run Code Online (Sandbox Code Playgroud)

该表达式实际上是一个列表,它有三个元素:

  • +符号
  • 数字 1
  • 数字 2

所有这些值都是程序员可以创建的正常值.创建1值非常容易,因为它会对自身进行求值:您只需键入1.但符号和列表更难:默认情况下,源代码中的符号执行变量查找!也就是说,符号不是自我评估的:

> 1
1
> a
a: undefined
  cannot reference undefined identifier
Run Code Online (Sandbox Code Playgroud)

事实证明,符号基本上只是字符串,实际上我们可以在它们之间进行转换:

> (string->symbol "a")
a
Run Code Online (Sandbox Code Playgroud)

列表甚至比符号更多,因为默认情况下,源代码中的列表会调用函数!否则(+ 1 2)在列表中的第一个元素的外观,该+符号,查找与之相关的功能,并在列表中的其余元素调用它.

但有时,您可能希望禁用此"特殊"行为.您可能只想获取列表或获取符号而不进行评估.为此,您可以使用quote.

报价的含义

考虑到这一切,很明显是什么quote:它只是"关闭"它包装的表达式的特殊评估行为.例如,考虑quote一个符号:

> (quote a)
a
Run Code Online (Sandbox Code Playgroud)

同样,考虑quote一个列表:

> (quote (a b c))
(a b c)
Run Code Online (Sandbox Code Playgroud)

无论你给予什么quote,它总会一直向你吐出来.不多也不少.这意味着如果你给它一个列表,那么子表达式都不会被评估 - 不要指望它们是!如果您需要任何类型的评估,请使用list.

现在,有人可能会问:如果您quote不是符号或列表,会发生什么?嗯,答案是......什么都没有!你回来了.

> (quote 1)
1
> (quote "abcd")
"abcd"
Run Code Online (Sandbox Code Playgroud)

这是有道理的,因为quote仍然只是吐出你给它的确切内容.这就是为什么像数字和字符串这样的"文字"在Lisp用语中有时被称为"自引用".

还有一件事:如果你quote的表达式包含什么会发生什么quote?那就是,如果你"加倍quote"怎么办?

> (quote (quote 3))
'3
Run Code Online (Sandbox Code Playgroud)

那里发生了什么?好吧,记住这'实际上只是一个直接的缩写quote,所以没有什么特别的事情发生!事实上,如果您的Scheme有办法在打印时禁用缩写,它将如下所示:

> (quote (quote 3))
(quote 3)
Run Code Online (Sandbox Code Playgroud)

不要quote因为特殊而被愚弄:就像(quote (+ 1)),这里的结果只是一个简单的旧列表.事实上,我们可以从列表中获取第一个元素:你能猜出它会是什么吗?

> (car (quote (quote 3)))
quote
Run Code Online (Sandbox Code Playgroud)

如果你猜对了3,那你错了.请记住,quote禁用所有评估,包含quote符号的表达式仍然只是一个普通列表.在REPL中玩这个,直到你对它感到满意为止.

> (quote (quote (quote 3)))
''3
(quote (1 2 (quote 3)))
(1 2 '3)
Run Code Online (Sandbox Code Playgroud)

报价非常简单,但由于它往往无视我们对传统评估模型的理解,因此它可能会变得非常复杂.事实上,它很简单,因为它有多么简单:没有特殊情况,没有规则.它只是准确地返回你给它的东西,正如所述(因此称为"引用").


附录A:准量

因此,如果报价完全禁用评估,它有什么用呢?好吧,除了提前提前知道的字符串,符号或数字列表之外,并不多.幸运的是,quasiquotation的概念提供了一种打破报价并重新进入普通评估的方法.

基础知识非常简单:不使用quote,而是使用quasiquote.通常情况下,这与quote各种方式完全相同:

> (quasiquote 3)
3
> (quasiquote x)
x
> (quasiquote ((a b) (c d)))
((a b) (c d))
Run Code Online (Sandbox Code Playgroud)

是什么让quasiquote特别是识别特殊符号unquote.无论unquote出现在列表中的哪个位置,它都会被它包含的任意表达式替换:

> (quasiquote (1 2 (+ 1 2)))
(1 2 (+ 1 2))
> (quasiquote (1 2 (unquote (+ 1 2))))
(1 2 3)
Run Code Online (Sandbox Code Playgroud)

这使您可以使用quasiquote构建具有"孔"的类别的模板来填充unquote.这意味着可以在引用列表中实际包含变量值:

> (define x 42)
> (quasiquote (x is: (unquote x)))
(x is: 42)
Run Code Online (Sandbox Code Playgroud)

当然,使用quasiquoteunquote相当冗长,所以他们有自己的缩写,就像'.具体来说,quasiquote`(反引号)并且unquote,(逗号).使用这些缩写,上面的例子更加可口.

> `(x is: ,x)
(x is: 42)
Run Code Online (Sandbox Code Playgroud)

最后一点:quasiquote实际上可以使用一个相当毛茸茸的宏在Racket中实现,它就是.它扩大到的用途list,cons以及当然quote.


附录B:实施listquote计划

list由于"rest argument"语法的工作原理,实现非常简单.这就是你所需要的:

(define (list . args)
  args)
Run Code Online (Sandbox Code Playgroud)

而已!

相比之下,quote要困难得多 - 实际上,这是不可能的!这看起来完全可行,因为禁用评估的想法听起来很像宏.然而,一个天真的尝试揭示了麻烦:

(define fake-quote
  (syntax-rules ()
    ((_ arg) arg)))
Run Code Online (Sandbox Code Playgroud)

我们只是采取arg并吐出来......但这不起作用.为什么不?好吧,我们的宏的结果将被评估,所以一切都是徒劳的.我们可以quote通过扩展到(list ...)并递归引用元素来扩展到某种类似的东西,如下所示:

(define impostor-quote
  (syntax-rules ()
    ((_ (a . b)) (cons (impostor-quote a) (impostor-quote b)))
    ((_ (e ...)) (list (impostor-quote e) ...))
    ((_ x)       x)))
Run Code Online (Sandbox Code Playgroud)

不幸的是,如果没有程序宏,我们就无法处理符号quote.我们可以近距离使用syntax-case,但即便如此,我们只会模仿quote行为,而不是复制它.


附录C:球拍打印惯例

在Racket中的这个答案中尝试这些例子时,您可能会发现它们没有像预期的那样打印.通常,他们可以使用前导打印',例如在此示例中:

> (list 1 2 3)
'(1 2 3)
Run Code Online (Sandbox Code Playgroud)

这是因为默认情况下,Racket会在可能的情况下将结果打印为表达式.也就是说,您应该能够将结果输入到REPL中并获得相同的值.我个人认为这种行为很好,但在尝试理解报价时可能会让人感到困惑,所以如果你想将其关闭,调用(print-as-expression #f)或更改打印样式以便在DrRacket语言菜单中"写入".

  • @Renzo我自己也有不同意见.如果它不是默认值,也许会更好.当然,我对开箱即用的推理知之甚少,所以我无法对此发表评论,但我绝对看到了你的观点并且同意它不止一点. (3认同)
  • 非常好的答案,我赞成它.但我不得不对DrRacket的默认打印行为持不同意见.我发现它存在问题有三个原因:1)它混淆了语言学习者的想法,比如这个问题以及其他问题(参见例如[Racket中的什么是'(撇号)?](http://stackoverflow.com/问题/ 33779637 /什么是撇号(球拍))清楚地表明; 2)它产生的结果可能是无意义的(用`(列表1(λ(x)(+ x 1))3)`系统打印''(1#<procedure> 3)`这是一个准-expression(!); 3)它与Scheme的所有其他实现不同. (2认同)

Kaz*_*Kaz 6

您所看到的行为是Scheme 不将符号视为函数的结果。

该表达式'(+ - * /)产生一个值,该值是符号列表。这只是因为(+ - * /) 一个符号列表,我们只是引用它来抑制求值,以便从字面上获取该对象作为值。

该表达式(list + - * /)生成一个函数列表。这是因为它是一个函数调用。符号表达式list+-和被求值*/它们都是表示函数的变量,因此被简化为这些函数。然后调用该list函数,并返回其余四个函数的列表。

在 ANSI Common Lisp 中,将符号作为函数调用是可行的:

[1]> (mapcar (lambda (f) (funcall f 1)) '(+ - * /))
(1 -1 1 1)
Run Code Online (Sandbox Code Playgroud)

当在需要函数的地方使用符号时,符号的顶级函数绑定将被替换(如果有的话),一切都很酷。实际上,符号是 Common Lisp 中可函数调用的对象。

如果您想使用list来生成符号列表,就像 一样'(+ - * /),您必须单独引用它们以抑制它们的求值:

(list '+ '- '* '/)
Run Code Online (Sandbox Code Playgroud)

回到计划世界,您会发现,如果您map超过了这一点,它将以与原始引用列表相同的方式失败。原因是相同的:尝试将符号对象用作函数。

您显示的错误消息具有误导性:

expected a procedure that can be applied to arguments
given: '+
Run Code Online (Sandbox Code Playgroud)

这里'+显示的是(quote +). 但这并不是申请的内容。它只是给出的+,问题是符号对象+不能用作该方言中的函数。

这里发生的事情是诊断消息正在+“打印为表达式”模式下打印符号,这是 Racket 的一个功能,我猜你正在使用它。

在“打印为表达式”模式下,使用必须读取和评估以生成类似对象的语法来打印对象。请参阅 StackOverflow 问题“为什么 Racket 解释器在前面编写带有撇号的列表?