报价单和清单有啥区别?

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

经验法则:只要你想评估参数,就使用listquote“分布”在它的参数上,所以'(+ 1 2)就像(list '+ '1 '2)。你最终会在你的列表中得到一个符号,而不是一个函数。


深入了解listquote

在 Scheme 和 Racket 中,quotelist完全不同的东西,但由于它们都可以用来生成列表,因此混淆是常见且可以理解的。它们之间有一个非常重要的区别:list 是一个普通的旧函数,而quote(即使没有特殊的' 语法)是一个特殊形式 .即list可以用普通Scheme实现,而quote不能。

list 函数

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

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

上面的例子可能会让人感到困惑,因为结果被打印为quoteable 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)

使用listx 在传递给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)

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

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

最后一点:准引用实际上可以在 Racket 中使用一个相当复杂的宏来实现,而且确实如此。它扩展到listcons,当然还有quote


附录 B:在 Scheme 中实现 listquote

实现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?”

【讨论】:

以上是关于报价单和清单有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

思睿信息技术服务部软件供货清单及报价

FOB报价是啥意思?

CDN带宽和IDC带宽有啥区别,为啥价格差距这

Ipad在保和过保回收报价

速达软件二次开发-隐藏货品资料成本单价和删除机会报价单

Salesforce PDF报价单制作