126

在阅读了 Lisp 入门书籍的主要部分之后,我仍然无法理解特殊运算符(quote)(或等效的')函数的作用,但这已经遍布我所见过的 Lisp 代码。

它有什么作用?

4

10 回答 10

192

简短回答 绕过默认评估规则,评估表达式(符号或 s-exp),将其传递给函数,完全按照键入的方式。

长答案:默认评估规则

当调用常规(我稍后会谈到)函数时,将评估传递给它的所有参数。这意味着你可以这样写:

(* (+ a 2)
   3)

进而(+ a 2)通过评估a和 2 来评估 。符号的值a在当前变量绑定集中查找,然后替换。Saya当前绑定到值 3:

(let ((a 3))
  (* (+ a 2)
     3))

我们会得到(+ 3 2),然后在 3 和 2 上调用 + 产生 5。我们的原始形式现在(* 5 3)产生 15。

已经解释quote了!

好的。如上所示,函数的所有参数都被评估,所以如果你想传递符号 a而不是它的值,你不想评估它。Lisp 符号既可以作为它们的值,也可以作为其他语言使用字符串的标记,例如哈希表的键。

这就是quote进来的地方。假设您想从 Python 应用程序中绘制资源分配,而是在 Lisp 中进行绘制。让您的 Python 应用程序执行以下操作:

print("'(")
while allocating:
    if random.random() > 0.5:
        print(f"(allocate {random.randint(0, 20)})")
    else:
        print(f"(free {random.randint(0, 20)})")
    ...
print(")")

给你的输出看起来像这样(稍微漂亮):

'((allocate 3)
  (allocate 7)
  (free 14)
  (allocate 19)
  ...)

还记得我所说的quote("tick") 导致默认规则不适用吗?好的。否则会发生的情况是 和 的值allocatefree查找,我们不希望这样。在我们的 Lisp 中,我们希望这样做:

(dolist (entry allocation-log)
  (case (first entry)
    (allocate (plot-allocation (second entry)))
    (free (plot-free (second entry)))))

对于上面给出的数据,将进行以下函数调用序列:

(plot-allocation 3)
(plot-allocation 7)
(plot-free 14)
(plot-allocation 19)

但是list呢?

好吧,有时您确实想评估这些论点。假设您有一个漂亮的函数来操作一个数字和一个字符串并返回一个结果列表......事物。让我们做一个错误的开始:

(defun mess-with (number string)
  '(value-of-number (1+ number) something-with-string (length string)))

Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER (1+ NUMBER) SOMETHING-WITH-STRING (LENGTH STRING))

嘿!那不是我们想要的。我们希望有选择地评估一些参数,而将其他参数保留为符号。试试#2!

(defun mess-with (number string)
  (list 'value-of-number (1+ number) 'something-with-string (length string)))

Lisp> (mess-with 20 "foo")
(VALUE-OF-NUMBER 21 SOMETHING-WITH-STRING 3)

不只是quote,而是backquote

好多了!顺便说一句,这种模式在(大部分)宏中很常见,以至于有特殊的语法可以做到这一点。反引号:

(defun mess-with (number string)
  `(value-of-number ,(1+ number) something-with-string ,(length string)))

这就像 using quote,但可以选择通过在某些参数前面加上逗号来显式评估它们。结果等效于 using list,但如果您从宏生成代码,您通常只想评估返回的代码的一小部分,因此反引号更适合。对于较短的列表,list可以更具可读性。

嘿,你忘了quote

那么,这会给我们带来什么影响?哦,对了,quote实际上是做什么的?它只是返回未评估的参数!还记得我在开始时所说的关于常规函数的内容吗?事实证明,某些运算符/函数不需要评估它们的参数。例如 IF - 如果 else 分支没有被采用,你不希望它被评估,对吧?所谓的特殊运算符,连同宏,就是这样工作的。特殊运算符也是语言的“公理”——最小的规则集——你可以在其上通过以不同的方式将它们组合在一起来实现 Lisp 的其余部分。

回到quote,虽然:

Lisp> (quote spiffy-symbol)
SPIFFY-SYMBOL

Lisp> 'spiffy-symbol ; ' is just a shorthand ("reader macro"), as shown above
SPIFFY-SYMBOL

比较(在 Steel-Bank Common Lisp 上):

Lisp> spiffy-symbol
debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD "initial thread" RUNNING   {A69F6A9}>:
  The variable SPIFFY-SYMBOL is unbound.

Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-INT:SIMPLE-EVAL-IN-LEXENV SPIFFY-SYMBOL #<NULL-LEXENV>)
0] 

因为spiffy-symbol在当前范围内没有!

加起来

quote, backquote(带逗号),并且list是用于创建列表的一些工具,它们不仅是值列表,而且如您所见,可以用作轻量级(无需定义 a struct)数据结构!

如果您想了解更多信息,我推荐 Peter Seibel 的书Practical Common Lisp以提供学习 Lisp 的实用方法,如果您已经开始全面编程的话。最终,在您的 Lisp 之旅中,您也将开始使用包。Ron Garret 的The Idiot's Guide to Common Lisp Packages将为您提供很好的解释。

快乐黑客!

于 2008-09-26T04:27:46.213 回答
57

它说“不要评价我”。例如,如果您想将列表用作数据而不是代码,则应在其前面加上引号。例如,

(print '(+ 3 4))打印“(+ 3 4)”,而 (print (+ 3 4))打印“7”

于 2008-09-25T18:02:28.977 回答
19

其他人已经很好地回答了这个问题,Matthias Benkard 提出了一个很好的警告。

不要使用报价来创建您稍后将修改的列表。该规范允许编译器将带引号的列表视为常量。通常,编译器会通过在内存中为常量创建单个值,然后从常量出现的所有位置引用该单个值来优化常量。换句话说,它可以将常量视为匿名全局变量。

这可能会导致明显的问题。如果你修改一个常量,它很可能会在完全不相关的代码中修改同一个常量的其他用途。例如,您可以在某个函数中将某个变量与 '(1 1) 进行比较,而在一个完全不同的函数中,以 '(1 1) 开始一个列表,然后向其中添加更多内容。运行这些函数后,您可能会发现第一个函数不再正确匹配,因为它现在尝试将变量与第二个函数返回的 '(1 1 2 3 5 8 13) 进行比较。这两个函数是完全不相关的,但是由于使用了常量,它们会相互影响。甚至可能会发生更疯狂的不良影响,例如完全正常的列表迭代突然无限循环。

当您需要一个常量列表时使用引号,例如用于比较。当您要修改结果时使用列表。

于 2009-02-23T16:49:34.877 回答
14

这个问题的一个答案是 QUOTE “创建列表数据结构”。这不太对。QUOTE 比这更根本。事实上,QUOTE 是一个微不足道的运算符:它的目的是防止任何事情发生。特别是,它不会创造任何东西。

(QUOTE X) 所说的基本上是“什么都不做,给我 X”。X 不必是 (QUOTE (ABC)) 中的列表或 (QUOTE FOO) 中的符号。它可以是任何对象。实际上,评估由 (LIST 'QUOTE SOME-OBJECT) 生成的列表的结果将始终只返回 SOME-OBJECT,无论它是什么。

现在, (QUOTE (ABC)) 似乎创建了一个元素为 A、B 和 C 的列表的原因是这样的列表确实是它返回的内容;但是在评估 QUOTE 表单时,列表通常已经存在了一段时间(作为 QUOTE 表单的一个组件!),由加载器或读取器在执行代码之前创建。

这往往会使新手经常绊倒的一个含义是,修改由 QUOTE 表单返回的列表是非常不明智的。出于所有意图和目的,QUOTE 返回的数据被视为正在执行的代码的一部分,因此应被视为只读!

于 2008-09-25T21:16:50.773 回答
11

引用阻止执行或评估表单,而是将其转换为数据。通常,您可以通过对其进行评估来执行数据。

quote 创建列表数据结构,例如,以下是等价的:

(quote a)
'a

它还可以用于创建列表(或树):

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

您最好购买一本关于 lisp 的介绍性书籍,例如Practical Common Lisp(可在线阅读)。

于 2008-09-25T18:04:27.553 回答
3

在 Emacs Lisp 中:

可以引用什么?

列表和符号。

引用一个数字将计算为数字本身: '5与 相同5

当你引用列表时会发生什么?

例如:

'(one two)评估为

(list 'one 'two)评估为

(list (intern "one") (intern ("two"))).

(intern "one")创建一个名为“one”的符号并将其存储在“中央”哈希映射中,因此无论何时您说'one该符号"one"都会在该中央哈希映射中查找。

但什么是符号?

例如,在 OO 语言(Java/Javascript/Python)中,符号可以表示为具有name字段的对象,该字段是符号的名称,"one"如上所示,并且数据和/或代码可以与该对象相关联。

因此 Python 中的符号可以实现为:

class Symbol:
   def __init__(self,name,code,value):
       self.name=name
       self.code=code
       self.value=value

例如,在 Emacs Lisp 中,一个符号可以有 1) 与之关联的数据和(同时 - 对于同一个符号)2) 与之关联的代码 - 根据上下文,数据或代码被调用。

例如,在 Elisp 中:

(progn
  (fset 'add '+ )
  (set 'add 2)
  (add add add)
)

评估为4

因为(add add add)评估为:

(add add add)
(+ add add)
(+ 2 add)
(+ 2 2)
4

因此,例如,使用Symbol我们上面在 Python 中定义的类,这个addELisp-Symbol 可以用 Python 编写为Symbol("add",(lambda x,y: x+y),2).

非常感谢 IRC #emacs 上的人们向我解释符号和引号。

于 2016-02-28T12:30:09.380 回答
2

当我们想传递参数本身而不是传递参数的值时,我们使用引号。它主要与使用 C 编程语言中不可用的列表、对和原子期间传递的过程有关(大多数人开始使用 C 编程进行编程,因此我们感到困惑)这是 Scheme 编程语言中的代码,它是 lisp 的一种方言我想你可以理解这段代码。

(define atom?              ; defining a procedure atom?
  (lambda (x)              ; which as one argument x
(and (not (null? x)) (not(pair? x) )))) ; checks if the argument is atom or not
(atom? '(a b c)) ; since it is a list it is false #f

最后一行 (atom?'abc) 将 abc 原样传递给程序以检查 abc 是否为原子,但是当您传递 (atom?abc) 时,它会检查 abc 的值并将值传递给它。因为,我们没有为它提供任何价值

于 2014-06-07T05:22:47.063 回答
2
Code is data and data is code.  There is no clear distinction between them.

这是任何 lisp 程序员都知道的经典语句。

当您引用代码时,该代码将是数据。

1 ]=> '(+ 2 3 4)
;Value: (+ 2 3 4)

1 ]=> (+ 2 3 4)
;Value: 9

当您引用代码时,结果将是代表该代码的数据。因此,当您要处理表示程序的数据时,您引用该程序。这也适用于原子表达式,不仅适用于列表:

1 ]=> 'code
;Value: code

1 ]=> '10
;Value: 10

1 ]=> '"ok"
;Value: "ok"

1 ]=> code
;Unbound variable: code

假设您想创建一种嵌入到 lisp 中的编程语言——您将使用在 scheme 中引用的程序(如'(+ 2 3)),并通过为程序提供语义解释来解释为您创建的语言中的代码。在这种情况下,您需要使用引号来保留数据,否则它将以外部语言进行评估。

于 2019-06-18T16:19:33.467 回答
1

Quote 返回其参数的内部表示。在对引用无效的解释进行了太多解释之后,这就是灯泡亮起的时候。如果在我引用函数名时 REPL 没有将函数名转换为大写字母,那么我可能还没有意识到。

所以。普通的 Lisp 函数将它们的参数转换为内部表示,计算参数并应用函数。Quote 将其参数转换为内部表示,然后返回。从技术上讲,这句话说“不评估”是正确的,但是当我试图理解它做了什么时,告诉我它没有做什么是令人沮丧的。我的烤面包机也不评估 Lisp 函数。但这不是您解释烤面包机功能的方式。

于 2015-05-12T00:13:02.023 回答
1

另一个简短的答案:

quote表示不评估它,反引号是引用但留下后门

一个很好的参考:

Emacs Lisp 参考手册说得很清楚

9.3 报价

特殊形式的引号返回其单个参数,如所写,而不对其进行评估。这提供了一种在程序中包含不是自评估对象的常量符号和列表的方法。(不必引用数字、字符串和向量等自评估对象。)

特殊形式:引用对象

This special form returns object, without evaluating it. 

因为引号在程序中经常使用,所以 Lisp 为它提供了一种方便的读取语法。一个撇号字符 (''') 后跟一个 Lisp 对象(在读取语法中)扩展为一个列表,其第一个元素是引号,第二个元素是对象。因此,读语法 'x 是 (quote x) 的缩写。

以下是一些使用引号的表达式示例:

(quote (+ 1 2))
     ⇒ (+ 1 2)

(quote foo)
     ⇒ foo

'foo
     ⇒ foo

''foo
     ⇒ (quote foo)

'(quote foo)
     ⇒ (quote foo)

9.4 反引号

反引号结构允许您引用列表,但有选择地评估该列表的元素。在最简单的情况下,它与特殊形式的引号相同(在上一节中描述;请参阅引用)。例如,这两种形式产生相同的结果:

`(a list of (+ 2 3) elements)
     ⇒ (a list of (+ 2 3) elements)

'(a list of (+ 2 3) elements)
     ⇒ (a list of (+ 2 3) elements)

反引号参数中的特殊标记“,”表示一个不是常量的值。Emacs Lisp 求值器求值 ',' 的参数,并将值放入列表结构中:

`(a list of ,(+ 2 3) elements)
     ⇒ (a list of 5 elements)

在列表结构的更深层次上也允许使用 ',' 进行替换。例如:

`(1 2 (3 ,(+ 4 5)))
     ⇒ (1 2 (3 9))

您还可以使用特殊标记“,@”将评估值拼接到结果列表中。拼接列表的元素成为与结果列表的其他元素处于同一级别的元素。不使用 '`' 的等效代码通常是不可读的。这里有些例子:

(setq some-list '(2 3))
     ⇒ (2 3)

(cons 1 (append some-list '(4) some-list))
     ⇒ (1 2 3 4 2 3)

`(1 ,@some-list 4 ,@some-list)
     ⇒ (1 2 3 4 2 3)
于 2016-02-21T22:01:07.080 回答