何时在Lisp中使用单引号(或引号)?

133
在阅读了一本入门Lisp书籍的主要部分后,我仍然无法理解特殊运算符(quote)(或等效的')函数是做什么用的,尽管我已经在看到的所有Lisp代码中都看到了它。
它是做什么用的?
10个回答

202

简短回答

绕过默认的评估规则并且不去评估表达式(符号或者s-exp),将其原样传递给函数。

长回答:默认评估规则

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

(* (+ a 2)
   3)

接着通过对a和2求值来计算(+ a 2)。符号a的值在当前变量绑定集合中查找并替换。假设a当前绑定了值3:

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

我们得到了(+ 3 2),然后在3和2上调用+运算符得到5。我们的原始形式现在是(* 5 3),得到15。

解释一下quote吧!

好吧。如上所述,函数的所有参数都会被评估,因此如果您想传递符号a而不是它的值,您不希望对其进行评估。 Lisp符号既可以作为它们的值,也可以作为标记,在其他语言中会使用字符串,例如哈希表的键。

这就是quote发挥作用的地方。假设您想在Lisp中绘制Python应用程序的资源分配情况。让您的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(“引号”)导致默认规则不适用的事吗?好的。否则会发生的情况是查找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))

嘿! 那不是我们想要的。 我们想要有选择地评估一些参数,并将其他参数保留为符号。尝试第二个方法!

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

不只是 单引号,还有反引号

好多了!顺便提一下,这种模式在(大多数情况下)宏中非常常见,因此有特殊的语法来实现这个功能。使用反引号:

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

这就像使用quote一样,但有一个选项可以用逗号作为前缀来显式地评估某些参数。结果相当于使用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

总结

quotebackquote(带逗号)和 list 是您用来创建列表的一些工具,它们不仅是值的列表,而且可以像轻量级(无需定义 struct)数据结构一样使用,正如您所见!

如果您想了解更多信息,我建议Peter Seibel的书《实用Common Lisp》,如果您已经涉足编程领域。最终在您的Lisp之旅中,您也会开始使用包。Ron Garret的《Common Lisp包白痴指南》将为您解释这些。

愉快的编码吧!


在我的emacs中,我已经设置好了SBCL。当我输入 'this 'is 'true 时,它只返回最后一个即TRUE的输出。即使在portacle中,我也得到相同的输出。 - Totoro
@Totoro 在 Lisp 中,函数的返回值或者多个语句的返回值是最后一个表达式,因此它实际上返回了 thisistrue,但你只能看到最后一个被返回的值。(thisistrue 是独立的语句) - Wezl'

57
它说“不要评估我”。例如,如果你想将一个列表用作数据而非代码,你需要在其前面加上引号。例如, (print '(+ 3 4))会打印出“(+ 3 4)”,而 (print (+ 3 4))会打印出“7”。

那么如何对其进行评估呢?例如,是否有一个“取消引用”命令? - Lime
4
@William LispsжңүдёҖдёӘж–№дҫҝзҡ„еҮҪж•°еҸ«еҒҡeval: (print (eval '(+ 3 4)))гҖӮиҝҷд№ҹжҳҜLispsеҰӮжӯӨдјҹеӨ§зҡ„еҺҹеӣ пјҡеҲ—иЎЁе°ұжҳҜд»Јз ҒпјҢиҖҢд»Јз Ғе°ұжҳҜеҲ—иЎЁпјҢеӣ жӯӨLispзЁӢеәҸеҸҜд»Ҙж“ҚзәөиҮӘиә«гҖӮ - darkfeline

20

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

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

这可能会导致明显的问题。如果您修改常量,那么它很可能会修改完全不相关代码中使用相同常量的其他内容。例如,您可能会在某个函数中将某个变量与'(1 1)进行比较,并且在完全不同的函数中,以'(1 1)开头并添加更多内容。运行这些函数后,您可能会发现第一个函数无法正确匹配,因为它现在尝试将变量与“(1 1 2 3 5 8 13)”进行比较,这是第二个函数返回的结果。这两个功能完全不相关,但它们的使用常量会相互影响。更糟糕的效果可能发生,例如完全正常的列表迭代突然进入无限循环。

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


听起来你应该大多数情况下使用(list (+ 1 2))。如果是这样,你如何防止在这种情况下对(+ 1 2)进行求值?有没有一个unquote命令? - Lime
1
你想要 '((3)) 的等效形式,还是想要 '((+ 1 2)) 的等效形式?如果是后者,你需要使用更多的 list(list (list '+ 1 2))。或者如果你想要 '(+ 1 2) 的等效形式,只需使用 (list '+ 1 2) 即可。记住,如果你不修改列表,可以放心地使用引用:如果你只是与它进行比较或其他操作,'(+ 1 2) 没有任何问题。 - Xanthir
1
你介意引用的列表被视为常量时应该在哪里进行处理吗? - Lime
HyperSpec http://clhs.lisp.se/Body/s_quote.htm 表示,如果引用的对象被破坏性地修改,则其行为是未定义的。这意味着允许实现将值视为原子值。 - Xanthir

14

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

(QUOTE X) 的含义基本上是“什么都不要做,只给我X。” X不必像(QUOTE (A B C))或者(QUOTE FOO)一样是一个列表或符号。 它可以是任何对象。 事实上,求值(LIST 'QUOTE SOME-OBJECT)产生的列表总是返回SOME-OBJECT,无论它是什么。

现在,(QUOTE (A B C))之所以看起来好像创建了一个元素为A,B和C的列表,是因为它确实返回了这样的一个列表; 但是在评估QUOTE表单时,该列表通常已经存在很长时间(作为QUOTE表单的组成部分!),在代码执行之前由加载程序或读取器创建。

这导致的一个影响是,新手往往容易被绊倒,认为可以修改由QUOTE表单返回的列表。 对于所有实际目的而言,由QUOTE返回的数据应被视为正在执行的代码的一部分,并且因此应被视为只读!


11

引用(quote)防止表单的执行或评估,而将其转换为数据。通常,您可以通过 eval 命令来执行该数据。

引用(quote)创建列表数据结构,例如以下内容是等效的:

(quote a)
'a

它还可以用来创建列表(或树形结构):

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

你最好先获取一本Lisp入门书籍,比如《实用的通用Lisp》(可在线阅读)。


3

在Emacs Lisp中:

什么可以被引用?

列表和符号。

引用数字会得到数字本身的值: '5 等同于 5

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

例如:

'(one two) 的求值结果为

(list 'one 'two),其求值结果为

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

(intern "one") 创建了一个名为"one"的符号,并将其存储在“中央”哈希映射表中,因此每次你说 'one 时,就会在该中央哈希映射表中查找名为 "one" 的符号。

但是什么是符号?

例如,在面向对象语言(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

例如,使用我们在Python中定义的Symbol类,这个add ELisp-Symbol可以在Python中写成Symbol("add",(lambda x,y: x+y),2)

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


3
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)),并通过给程序语义解释来解释为所创建的语言中的代码。在这种情况下,您需要使用引号来保留数据,否则它会在外部语言中被评估。

2
另一个简短的回答是:quote意味着不进行评估,而backquote是quote,但留下后门。
一个好的参考资料是:Emacs Lisp参考手册非常清晰地解释了它。
9.3 引用
特殊形式quote返回其单个参数,如所写的那样,而不对其进行评估。这提供了一种在程序中包含常量符号和列表(它们不是自我评估对象)的方法。(不需要引用自我评估对象,如数字、字符串和向量。)
特殊形式:引用对象
This special form returns object, without evaluating it. 

因为引用在程序中使用非常频繁,Lisp为其提供了方便的读取语法。一个撇号字符(')后跟一个Lisp对象(在读取语法中)会扩展为一个列表,其第一个元素是quote,第二个元素是该对象。因此,读取语法'x是(quote x)的缩写。
以下是一些使用引用的表达式示例:
(quote (+ 1 2))
     ⇒ (+ 1 2)

(quote foo)
     ⇒ foo

'foo
     ⇒ foo

''foo
     ⇒ (quote foo)

'(quote foo)
     ⇒ (quote foo)

9.4 反引号

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

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

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的值并将该值传递给过程。由于我们没有为其提供任何值,因此它会返回一个错误。

1

Quote返回其参数的内部表示。在阅读了太多关于quote不做什么的解释后,我才恍然大悟。如果REPL在我引用函数名称时没有将它们转换为大写,我可能就不会明白了。

所以,普通的Lisp函数将其参数转换为内部表示,然后评估参数并应用函数。Quote将其参数转换为内部表示,然后只是返回该表示。从技术上讲,说quote表示“不要评估”是正确的,但当我试图理解它的时候,告诉我它不做什么是很令人沮丧的。我的烤面包机也不会评估Lisp函数;但这不是你解释烤面包机的方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接