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

时间:2008-09-25 17:59:42

标签: lisp quote

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

它做了什么?

10 个答案:

答案 0 :(得分:162)

简短回答 绕过默认评估规则并执行 not 评估表达式(symbol或s-exp),将其与函数完全一样传递给函数。

长答案:默认评估规则

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

(* (+ a 2)
   3)

通过评估(+ a 2)和2来反过来评估a。在当前变量绑定集中查找符号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(“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)))

这就像使用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的The Idiot's Guide to Common Lisp Packages会给你很好的解释。

快乐的黑客攻击!

答案 1 :(得分:49)

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

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

答案 2 :(得分:17)

其他人对这个问题的回答令人钦佩,Matthias Benkard提出了一个很好的警告。

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

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

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

答案 3 :(得分: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返回的数据被视为正在执行的代码的一部分,因此应被视为只读!

答案 4 :(得分:11)

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

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

(quote a)
'a

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

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

你可能最好收到关于lisp的介绍性书籍,例如Practical Common Lisp(可以在线阅读)。

答案 5 :(得分: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

最后一行(原子?&#39; abc)正在传递abc,因为它是检查abc是否是原子的过程,但是当你传递(atom?abc)然后它检查的值是abc并将值传递给它。因为,我们还没有为它提供任何价值

答案 6 :(得分:2)

在Emacs Lisp中:

可以引用什么?

列表和符号。

引号会评估数字本身: '55相同。

引用列表时会发生什么?

例如:

'(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)与之关联的数据AND(同时 - 对于相同的符号)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上的人们向我解释符号和引号。

答案 7 :(得分:0)

Quote返回其参数的内部表示。在通过对所做的引用的过多解释之后,那就是灯泡继续发生的时候。如果我引用它们时REPL没有将函数名转换为UPPER-CASE,那么它可能就不会出现了。

因此。普通的Lisp函数将其参数转换为内部表示,计算参数并应用函数。 Quote将其参数转换为内部表示,然后返回。从技术上来说,引用说“不评估”是正确的,但当我试图理解它做了什么时,告诉我它不做的事情令人沮丧。我的烤面包机也没有评估Lisp函数;但这不是你怎么解释烤面包机的作用。

答案 8 :(得分:0)

Anoter简短回答:

quote表示不进行评估,而反引用是引用但留下后门

好参考:

Emacs Lisp参考手册非常清楚

9.3报价

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

特殊表格:报价对象

This special form returns object, without evaluating it. 

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

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

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

(quote foo)
     ⇒ foo

'foo
     ⇒ foo

''foo
     ⇒ (quote foo)

'(quote foo)
     ⇒ (quote foo)

9.4 Backquote

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

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

答案 9 :(得分:0)

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)),并通过给程序一个语义解释。在这种情况下,您需要使用引号保存数据,否则将使用外部语言对其进行评估。

相关问题