什么时候在Lisp中使用'(或引用)?


114

在介绍Lisp入门书的主要部分之后,我仍然不明白特殊运算符是什么 (quote)(或等效')的功能,但是我所看到的都是Lisp代码。

它有什么作用?

Answers:


178

简短答案 绕过默认的求值规则,并且求值表达式(符号或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。假设您想从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(“勾号”)导致默认规则不适用的说法吗?好。否则将发生的情况是查找allocate和的值free,而我们不希望那样。我们希望在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)))

就像使用一样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)的《Practical Common Lisp》一书,这是一种学习Lisp的实用方法,如果您已经开始进行广泛的编程。最终,在Lisp旅程中,您还将开始使用软件包。Ron Garret的“常见Lisp包的白痴指南”将为您提供很好的解释。

骇客骇客!


在我的emacs SBCL中进行设置,当我输入`'this'为'true`时,它仅返回最后一个,即TRUE。即使在运动中,我也得到相同的输出
龙猫

@Totoro函数或lisp中的多个语句的返回值是最后一个表达式,因此它实际上返回this,然后是is,然后是true,但是您只能看到返回的最后一个。(这是正确的,是单独的声明)
Wezl

52

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

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


那么可能如何评估它,例如是否有unquote命令?
2015年

3
@William Lisps具有一个方便的功能,称为eval(print (eval '(+ 3 4)))。这就是Lisps如此出色的原因:列表是代码,而代码是列表,因此Lisp程序可以进行自我操作。
darkfeline 2015年

18

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

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

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

需要常量列表(例如进行比较)时,请使用引号。修改结果时使用列表。


因此,听起来(list (+ 1 2)) 大多数时候都应该使用。如果是这样,您如何防止(+ 1 2)在这样一个示例内部进行评估?有unquote命令吗?
2015年

1
您是否想要的等价'((3))'((+ 1 2))?如果是后者,则必须使用更多list(list (list '+ 1 2))。或者,如果你想的等效'(+ 1 2),只是(list '+ 1 2)。记住,如果您不修改列表,请随时使用quote:'(+ 1 2)如果只是将其与之比较,那没有什么错。
Xanthir

1
您介意将引用列表视为常量的地方引用吗?
2015年

HyperSpec clhs.lisp.se/Body/s_quote.htm说,如果对引用的对象进行破坏性修改,则行为未定义。暗示这是为了允许impls将值视为原子值。
Xanthir

14

这个问题的一个答案是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返回的数据均应被视为正在执行代码的因此应将其视为只读!


11

引用防止执行或评估表单,而是将其转换为数据。通常,您可以通过评估然后执行数据。

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"就会在该中央哈希图中查找名为的符号。

但是什么是符号?

例如,在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中定义的类,可以使用Python将此addELisp-Symbol编写为Symbol("add",(lambda x,y: x+y),2)

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


2

当我们想要传递参数本身而不是传递参数值时,我们使用quote。它主要与使用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返回其参数的内部表示。在仔细研究过很多关于引号不起作用的解释之后,灯泡就亮了。如果在引用引号时REPL并未将函数名转换为大写,则可能对我而言还不是很早。

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


1

简短回答:

quote表示不对其进行评估,并且backquote是quote但留下后门

一个很好的参考:

Emacs Lisp参考手册非常清楚

9.3报价

特殊形式的引号按原样返回其单个参数,而不对其求值。这提供了一种在程序中包括不是自评估对象的常量符号和列表的方法。(没有必要引用自评估对象,例如数字,字符串和向量。)

特殊形式:引用对象

This special form returns object, without evaluating it. 

由于在程序中经常使用quote,因此Lisp为它提供了一种方便的读取语法。撇号字符(''')后跟Lisp对象(以读取语法)会扩展到一个列表,该列表的第一个元素为quote,第二个元素为object。因此,读取的语法'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)

1
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中的编程语言-您将使用方案(如'(+ 2 3))中引用的程序,并通过给程序一种语义解释来解释为所创建语言中的代码。在这种情况下,您需要使用引号保存数据,否则将使用外部语言对其进行评估。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.