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

时间:2020-03-06 14:44:17  来源:igfitidea点击:

在介绍Lisp入门书籍的主要部分之后,我仍然不明白特殊运算符(quote)(或者等效的`'``)函数的作用,但是这遍历了Lisp代码见过。

它有什么作用?

解决方案

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

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

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

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

(quote a)
'a

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

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

我们最好获得有关Lisp的入门书,例如《实践普通Lisp》(可在线阅读)。

这个问题的一个答案是QUOTE创建列表数据结构。这不太正确。报价比这更根本。实际上,QUOTE是一个琐碎的运算符:它的目的是完全防止任何事情发生。特别是,它不会创建任何东西。

(QUOTE X)所说的基本上什么也没做,只要给我X就可以了。它可以是任何对象。实际上,评估(LIST'QUOTE SOME-OBJECT)产生的列表的结果将始终只是返回SOME-OBJECT,无论它是什么。

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

这往往导致新手跳闸的一个隐含含义是,修改QUOTE表单返回的列表是非常不明智的。出于所有目的和目的,由QUOTE返回的数据应被视为正在执行的代码的一部分,因此应将其视为只读!

简短答案
绕过默认的求值规则,并且不求值表达式(符号或者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 "(allocate %d)" random.randint(0, 20)
    else:
        print "(free %d)" % 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

加起来

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

如果我们想了解更多信息,我建议彼得·塞贝尔(Peter Seibel)的《 Practical Common Lisp》一书,这是一种学习Lisp的实用方法,如果我们已经开始进行广泛的编程。最终,在Lisp旅程中,我们也将开始使用软件包。 Ron Garret的《常见Lisp包的白痴指南》将为我们提供很好的解释。

祝我们黑客愉快!

其他人对此问题的回答令人钦佩,马蒂亚斯·本卡尔德(Matthias Benkard)提出了一个很好的警告。

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

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

需要常量列表(例如用于比较)时,请使用quote。修改结果时使用列表。