什么时候在Lisp中使用'(或者引用)?
在介绍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。修改结果时使用列表。