报价和列表有什么区别?
我知道你可以使用'
(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)
。 你最终会在你的列表中有一个符号,而不是一个函数。
深入了解list
和quote
在Scheme和Racket中, quote
和list
是完全不同的东西 ,但是由于它们都可以用来生成列表,所以混淆是常见的和可以理解的。 他们之间有一个非常重要的区别: list
是一个普通的旧function ,而quote
(即使没有特殊'
语法)是一种特殊的forms 。 也就是说, list
可以在普通的Scheme中实现,但quote
不可以。
list
function
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)
有了list
, x
在得到传递给list
之前就被评估了。 quote
,事情更复杂。
最后,因为list
只是一个函数,所以它可以像其他任何函数一样使用,包括更高级的方法。 例如,它可以传递给map
function,它会适当地工作:
> (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)
当然,使用quasiquote
和unquote
是相当冗长的,所以他们有自己的缩写,就像'
。 具体来说, quasiquote
是(反向),而不是(逗号)。 用这些缩写,上面的例子更可口。
> `(x is: ,x) (x is: 42)
最后一点:quasiquote实际上可以用一个相当毛茸茸的macros在Racket中实现,而且是这样。 它扩大到list
, cons
,当然, quote
用法。
附录B:在计划中实施list
和quote
实现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”语言菜单。