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

在阅读了 Lisp 入门书籍的主要部分之后,我仍然不能理解特殊的操作符 (quote)(或者等效的 ')函数是做什么的,但是我所看到的 Lisp 代码都是这样。

它有什么用?

46524 次浏览

上面写着“不要评价我”。例如,如果希望将列表用作数据,而不是代码,那么应该在列表前面加上引号。比如说,

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

引号阻止窗体的执行或计算,而是将其转换为数据。一般来说,您可以先执行数据,然后再对其进行求值。

引号创建列表数据结构,例如,下面是等价的:

(quote a)
'a

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

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

您可能最好获得一本关于 lisp 的介绍性书籍,例如 实用 Common Lisp(可以在线阅读)。

这个问题的一个答案是 QUOTE“创建了列表数据结构”。这不太对。QUOTE 比这更基本。实际上,QUOTE 只是一个普通的操作符: 它的目的是防止 预防发生任何事情。特别是,它不创造任何东西。

(QUOTE X)所说的基本上是“不要做任何事情,只要给我 X。”X 不必是(QUOTE (AB C))中的列表或(QUOTE FOO)中的符号。它可以是任何物体。事实上,计算由(LIST‘ QUOTE Some-Object)生成的列表的结果总是返回 Some-Object,不管它是什么。

现在,(QUOTE (AB 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(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))

嘿!这不是我们想要的。我们希望 有选择地计算一些参数,而把其他的作为符号。试试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 的书 实用 Common Lisp,如果你已经在广泛的编程中,这是一个学习 Lisp 的实用方法。最终在您的 Lisp 之旅中,您也将开始使用包。Ron Garret 的 普通 Lisp 包的傻瓜指南会给你很好的解释。

黑客愉快!

其他人对这个问题的回答令人钦佩,马蒂亚斯 · 本卡德提出了一个极好的警告。

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

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

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

当我们想要传递一个参数本身,而不是传递参数的值时,那么我们使用引号。它主要与在使用列表、对和原子时传递的过程有关 这在 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

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

Quote 返回其参数的内部表示形式。在仔细研究了太多关于引用 没有的解释之后,我们终于明白了什么是 没有。如果在我引用函数名时,REPL 没有将函数名转换为大写,我可能就不会明白了。

那么。普通 Lisp 函数将它们的参数转换为内部表示形式,计算参数,并应用函数。Quote 将其参数转换为内部表示形式,然后返回。从技术上来说,这句话是正确的,“不要评估”,但是当我试图理解它做了什么,告诉我它不做什么是令人沮丧的。我的烤面包机也不能计算 Lisp 函数,但是这不是解释烤面包机的原理。

另一个简短的回答是:

quote意味着没有评估它,而 反引号是引号,但留下 后门

一个很好的参考:

Emacs Lisp 参考手册说得很清楚

9.3引述

特殊形式的引号返回其写入的单个参数,而不计算它。这提供了一种在程序中包含常量符号和列表的方法,这些符号和列表不是自我计算对象。(不需要引用自我计算对象,如数字、字符串和向量。)

特殊格式: 引用对象

This special form returns object, without evaluating it.

因为引号在程序中使用得非常频繁,Lisp 为它提供了一种方便的读取语法。后跟一个 Lisp 对象(在读语法中)的撇号字符(‘’’)展开为一个列表,其中第一个元素是 quote,第二个元素是 object。因此,read 语法’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)

在 Emacs Lisp 中:

可以引用什么?

列表和符号。

引用一个数字等于这个数字本身: '55相同。

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

例如:

'(one two)评估为

计算结果为

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

(intern "one")创建一个名为“ one”的符号并将其存储在一个“ central”hash-map 中,因此任何时候您说 'one,那么名为 "one"的符号将在该中心 hash-map 中查找。

但什么是符号?

例如,在面向对象语言(Java/Javascript/Python)中,一个符号可以表示为一个具有 name字段的对象,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-Sign 可以用 Python 编写为 Symbol("add",(lambda x,y: x+y),2)

非常感谢 IRC # emacs 上的朋友们为我解释符号和引号。

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 中的编程语言——您将处理在 schema 中引用的程序(如 '(+ 2 3)) ,这些程序在您创建的语言中被解释为代码,方法是为程序提供语义解释。在这种情况下,您需要使用引号来保存数据,否则将使用外部语言对其进行计算。