报价和列表有什么区别?

我知道你可以使用' (aka quote'来创build一个列表,并且我一直使用它,就像这样:

 > (car '(1 2 3)) 1 

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

 > (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) 

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

TL; DR:他们不一样; 有疑问时使用list

经验法则:每当你想要评估参数时使用list ; quote “分布”在其论据上,所以'(+ 1 2)就像(list '+ '1 '2) 。 你最终会在你的列表中有一个符号,而不是一个函数。


深入了解listquote

在Scheme和Racket中, quotelist完全不同的东西 ,但是由于它们都可以用来生成列表,所以混淆是常见的和可以理解的。 他们之间有一个非常重要的区别: list是一个普通的旧function ,而quote (即使没有特殊'语法)是一种特殊的forms 。 也就是说, list可以在普通的Scheme中实现,但quote不可以。

listfunction

list函数实际上是两者中较为简单的,所以我们从这里开始。 它是一个接受任意数量参数的函数,它将参数收集到列表中。

 > (list 1 2 3) (1 2 3) 

上面这个例子可能会让人困惑,因为结果被打印为一个可quote sexpression式,这是真的,在这种情况下,这两个语法是等价的。 但是,如果我们稍微复杂一点,你会发现它是不同的:

 > (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)这样的expression式在被收集到列表中之前将减less到2

向列表函数提供variables时,这种行为也是可见的:

 > (define x 42) > (list x) (42) > '(x) (x) 

有了listx在得到传递给list之前就被评估了。 quote ,事情更复杂。

最后,因为list只是一个函数,所以它可以像其他任何函数一样使用,包括更高级的方法。 例如,它可以传递给mapfunction,它会适当地工作:

 > (map list '(1 2 3) '(4 5 6)) ((1 4) (2 5) (3 6)) 

quote

list不同,引用是Lisp的一个特殊部分。 quote表是特殊的,因为它得到了一个特殊的读者缩写, '但是它也是特殊的,即使没有。 与list不同, quote 不是一个函数,因此它不需要像一个行为一样 – 它有它自己的规则。

Lisp源代码简要讨论

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

 (+ 1 2) 

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

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

所有这些值都是程序员可以创build的正常值。 创build1值是非常简单的,因为它是自己评估的:只需input1 。 但是符号和列表比较困难:默认情况下,源代码中的符号执行variables查找! 也就是说,符号不是自我评估的

 > 1 1 > a a: undefined cannot reference undefined identifier 

事实certificate,虽然符号基本上只是string,事实上我们可以在它们之间进行转换:

 > (string->symbol "a") a 

列表甚至超过符号,因为默认情况下,源代码中的列表调用一个函数!(+ 1 2)查看列表中的第一个元素, +符号,查找与其关联的函数,并用列表中的其余元素调用它。

有时候,你可能想要禁用这个“特殊”的行为。 您可能只想获得列表或获取符号,而不进行评估。 要做到这一点,你可以使用quote

报价的含义

考虑到这一切, quote含义非常明显:它只是“closures”它所包装expression式的特殊评估行为。 例如,请考虑quote一个符号:

 > (quote a) a 

同样,考虑quote一个列表:

 > (quote (abc)) (abc) 

不pipe你给出的是什么, 总是会把它吐在你身上。 不多不less。 这意味着如果你给它一个列表,没有一个子expression式将被评估 – 不要指望它们是! 如果您需要任何种类的评估,请使用list

现在,有人可能会问:如果quote符号或列表以外的内容会发生什么? 那么,答案是……什么都没有! 你只要把它拿回来。

 > (quote 1) 1 > (quote "abcd") "abcd" 

这是有道理的,因为quote仍然只是吐出你给的东西。 这就是为什么“文字”像数字和string有时在Lisp中被称为“自引用”的原因。

还有一件事:如果你quote一个包含quote的expression式会发生什么? 那就是,如果你“双quote ”呢?

 > (quote (quote 3)) '3 

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

 > (quote (quote 3)) (quote 3) 

不要被特殊的quote所迷惑,就像(quote (+ 1)) ,这里的结果只是一个普通的旧名单。 事实上,我们可以从列表中获得第一个元素:你能猜到它会是什么吗?

 > (car (quote (quote 3))) quote 

如果你猜了3 ,你错了。 请记住, quote禁用所有评估 ,并且包含quote符号的expression式仍然只是一个普通的列表。 在REPL中玩这个,直到你感觉舒服为止。

 > (quote (quote (quote 3))) ''3 (quote (1 2 (quote 3))) (1 2 '3) 

报价非常简单,但它可能会非常复杂,因为它往往不符合我们对传统评估模式的理解。 事实上,这很混乱, 因为它很简单:没有特殊情况,没有规则。 它只是返回你所给的,正如所述(因此名称“报价”)。


附录A:Quasiquotation

所以,如果报价完全禁用评估,有什么好处呢? 那么,除了提前列出所有已知string,符号或数字的列表之外,不要太多。 幸运的是, 准分类的概念提供了一种突破报价的方式,并回到了普通的评估中。

基础是非常简单的:而不是使用quote ,使用quasiquote 。 通常情况下,这种方式与各种方式完全一样:

 > (quasiquote 3) 3 > (quasiquote x) x > (quasiquote ((ab) (cd))) ((ab) (cd)) 

什么使quasiquote特别是认识一个特别标志, unquote 。 无论在哪里出现unquote ,它都会被它所包含的任意expression式取代:

 > (quasiquote (1 2 (+ 1 2))) (1 2 (+ 1 2)) > (quasiquote (1 2 (unquote (+ 1 2)))) (1 2 3) 

这使您可以使用quasiquote来构build具有“洞”的模板,并用非quasiquote来填充。 这意味着可以在引用列表中实际包含variables的值:

 > (define x 42) > (quasiquote (x is: (unquote x))) (x is: 42) 

当然,使用quasiquoteunquote是相当冗长的,所以他们有自己的缩写,就像' 。 具体来说, quasiquote是(反向),而不是(逗号)。 用这些缩写,上面的例子更可口。

 > `(x is: ,x) (x is: 42) 

最后一点:quasiquote实际上可以用一个相当毛茸茸的macros在Racket中实现,而且是这样。 它扩大到listcons ,当然, quote用法。


附录B:在计划中实施listquote

实现list是非常简单的,因为“rest argument”语法是如何工作的。 这是你需要的一切:

 (define (list . args) args) 

而已!

相比之下, quote更难 – 事实上,这是不可能的! 这看起来完全可行,因为closures评估的想法听起来像macros一样。 然而,一个天真的尝试揭示了这个麻烦:

 (define fake-quote (syntax-rules () ((_ arg) arg))) 

我们只是采取arg和吐出来…但这是行不通的。 为什么不? 那么,我们macros观的结果将被评估,所有这一切都是徒劳的。 我们可以通过扩展到(list ...)和recursion引用元素来扩展某种类似于引用的内容,如下所示:

 (define impostor-quote (syntax-rules () ((_ (a . b)) (cons (impostor-quote a) (impostor-quote b))) ((_ (e ...)) (list (impostor-quote e) ...)) ((_ x) x))) 

不幸的是,虽然没有程序macros,但是我们不能在没有quote情况下处理符号。 我们可以用syntax-case来更近一些,但即使如此,我们也只能模仿quote的行为,而不能复制quote的行为。


附录C:球拍打印公约

当在Racket中的这个答案中尝试这些例子时,您可能会发现它们并不像预期的那样打印。 通常,他们可能会打印一个领先' ,如在这个例子中:

 > (list 1 2 3) '(1 2 3) 

这是因为默认情况下,Racket尽可能地将结果打印为expression式。 也就是说,您应该能够将结果input到REPL中并获取相同的值。 我个人觉得这个行为很好,但是当试图理解引用时会引起混淆,所以如果你想closures引用,可以调用(print-as-expression #f) ,或者把(print-as-expression #f)的打印样式改为“write”语言菜单。