报价单和清单有啥区别?
Posted
技术标签:
【中文标题】报价单和清单有啥区别?【英文标题】:What is the difference between quote and list?报价单和清单有什么区别? 【发布时间】:2016-05-01 06:53:43 【问题描述】:我知道你可以使用'
(又名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)
为什么?我以为'
只是一个方便的简写,那为什么行为不同呢?
【问题讨论】:
(供参考:我创建这个是为了尝试为这种混乱制作一个规范的欺骗目标。我看到这类问题出现了很多。) 仍然可以引用:(define math-fns (map (lambda (s) (lambda args (eval (s . args) (environment '(rnrs))))) '(+ - * /)))
。
另外:您也不能在使用引号创建的列表中set-car!
或set-cdr!
。
【参考方案1】:
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)
上面的例子可能会让人感到困惑,因为结果被打印为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
只是一个函数,它可以像任何其他函数一样使用,包括高阶方式。例如,它可以传递给map
函数,它会正常工作:
> (map list '(1 2 3) '(4 5 6))
((1 4) (2 5) (3 6))
quote
表单
与list
不同,引号是 Lisps 的一个特殊部分。 quote
形式的特殊部分是因为它有一个特殊的读者缩写'
,但即使没有它,它也也特别。与list
不同,quote
不是一个函数,因此它不需要表现得像一个函数——它有自己的规则。
浅谈Lisp源码
在Scheme和Racket衍生的Lisp中,所有的代码实际上都是由普通的数据结构组成的。例如,考虑以下表达式:
(+ 1 2)
那个表达式实际上是一个列表,它包含三个元素:
+
符号
号码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 (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:准引用
如果引用完全禁用评估,它有什么用?好吧,除了列出提前知道的字符串、符号或数字之外,不多。幸运的是,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)
最后一点:准引用实际上可以在 Racket 中使用一个相当复杂的宏来实现,而且确实如此。它扩展到list
、cons
,当然还有quote
。
附录 B:在 Scheme 中实现 list
和 quote
实现list
非常简单,因为“rest argument”语法的工作原理。这就是你所需要的:
(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 语言菜单中将打印样式更改为“写”。
【讨论】:
非常好的答案,我赞成。但我不得不不同意 DrRacket 的默认打印行为。我发现这有三个原因:1)它混淆了语言学习者的想法,就像这个问题和其他问题(例如参见What is ' (apostrophe) in Racket?)清楚地表明; 2)它产生的结果可能是无意义的((list 1 (λ(x)(+ x 1)) 3)
系统打印'(1 #<procedure> 3)
这是一个准表达式(!);3)它不同于Scheme的所有其他实现。
@Renzo 我自己对此意见不一。如果不是默认设置可能会更好。当然,我对开箱即用的原因知之甚少,因此我无法对此发表评论,但我绝对理解您的观点并非常同意。
在“嵌套引用”部分我们应该有一个嵌套列表的例子吗?这应该显示'(1 2 '(3))
和(list 1 2 (list 3))
之间的区别,这可能是错误的。【参考方案2】:
您看到的行为是由于 Scheme 未将符号视为函数。
表达式'(+ - * /)
产生一个值,它是一个符号列表。这仅仅是因为(+ - * /)
是一个符号列表,我们只是引用它来抑制评估,以便将该对象作为一个值获得。
表达式(list + - * /)
产生一个函数列表。这是因为它是一个函数调用。评估符号表达式 list
、+
、-
、*
和 /
。它们都是表示函数的变量,因此被简化为那些函数。然后调用list
函数,并返回其余四个函数的列表。
在 ANSI Common Lisp 中,将符号作为函数调用是可行的:
[1]> (mapcar (lambda (f) (funcall f 1)) '(+ - * /))
(1 -1 1 1)
当在需要函数的地方使用符号时,将替换该符号的***函数绑定,如果它有一个,那么一切都很酷。实际上,符号是 Common Lisp 中的函数可调用对象。
如果您想使用list
生成符号列表,就像'(+ - * /)
一样,您必须单独引用它们以抑制它们的评估:
(list '+ '- '* '/)
回到Scheme世界,你会看到如果你map
超过这个,它将以与原始引用列表相同的方式失败。原因是一样的:试图将符号对象用作函数。
您看到的错误消息具有误导性:
expected a procedure that can be applied to arguments
given: '+
这里显示的'+
是(quote +)
。但这不是申请的内容;它只给出了+
,问题是符号对象+
在该方言中不能用作函数。
这里发生的情况是诊断消息正在“打印为表达式”模式下打印+
符号,这是 Racket 的一个功能,我猜你正在使用它。
在“打印为表达式”模式下,使用必须读取和评估的语法打印对象以生成类似的对象。请参阅 *** 问题“Why does the Racket interpreter write lists with an apostroph before?”
【讨论】:
以上是关于报价单和清单有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章