(quote)
(或等效的'
)函数是做什么用的,尽管我已经在看到的所有Lisp代码中都看到了它。它是做什么用的?
简短回答
绕过默认的评估规则并且不去评估表达式(符号或者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
发挥作用的地方。假设您想在Lisp中绘制Python应用程序的资源分配情况。让您的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))
嘿! 那不是我们想要的。 我们想要有选择地评估一些参数,并将其他参数保留为符号。尝试第二个方法!
(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)
不只是 单引号
,还有反引号
好多了!顺便提一下,这种模式在(大多数情况下)宏中非常常见,因此有特殊的语法来实现这个功能。使用反引号:
(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的书《实用Common Lisp》,如果您已经涉足编程领域。最终在您的Lisp之旅中,您也会开始使用包。Ron Garret的《Common Lisp包白痴指南》将为您解释这些。
愉快的编码吧!
(print '(+ 3 4))
会打印出“(+ 3 4)”,而
(print (+ 3 4))
会打印出“7”。eval
: (print (eval '(+ 3 4)))
гҖӮиҝҷд№ҹжҳҜLispsеҰӮжӯӨдјҹеӨ§зҡ„еҺҹеӣ пјҡеҲ—иЎЁе°ұжҳҜд»Јз ҒпјҢиҖҢд»Јз Ғе°ұжҳҜеҲ—иЎЁпјҢеӣ жӯӨLispзЁӢеәҸеҸҜд»Ҙж“ҚзәөиҮӘиә«гҖӮ - darkfeline其他人已经出色地回答了这个问题,Matthias Benkard提出了一个很好的警告。
不要使用引用符创建列表,然后再修改列表。指南允许编译器将带引号的列表视为常量。通常,编译器会通过在内存中创建单个值来优化常量,然后从所有出现常量的位置引用该单个值。换句话说,它可能将常量视为匿名全局变量。
这可能会导致明显的问题。如果您修改常量,那么它很可能会修改完全不相关代码中使用相同常量的其他内容。例如,您可能会在某个函数中将某个变量与'(1 1)进行比较,并且在完全不同的函数中,以'(1 1)开头并添加更多内容。运行这些函数后,您可能会发现第一个函数无法正确匹配,因为它现在尝试将变量与“(1 1 2 3 5 8 13)”进行比较,这是第二个函数返回的结果。这两个功能完全不相关,但它们的使用常量会相互影响。更糟糕的效果可能发生,例如完全正常的列表迭代突然进入无限循环。
当您需要一个常量列表(例如用于比较)时,请使用引号;当您将修改结果时,请使用列表。
(list (+ 1 2))
。如果是这样,你如何防止在这种情况下对(+ 1 2)
进行求值?有没有一个unquote
命令? - Lime'((3))
的等效形式,还是想要 '((+ 1 2))
的等效形式?如果是后者,你需要使用更多的 list
:(list (list '+ 1 2))
。或者如果你想要 '(+ 1 2)
的等效形式,只需使用 (list '+ 1 2)
即可。记住,如果你不修改列表,可以放心地使用引用:如果你只是与它进行比较或其他操作,'(+ 1 2)
没有任何问题。 - Xanthir这个问题的一个回答说 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返回的数据应被视为正在执行的代码的一部分,并且因此应被视为只读!
引用(quote)防止表单的执行或评估,而将其转换为数据。通常,您可以通过 eval 命令来执行该数据。
引用(quote)创建列表数据结构,例如以下内容是等效的:
(quote a)
'a
它还可以用来创建列表(或树形结构):
(quote (1 2 3))
'(1 2 3)
你最好先获取一本Lisp入门书籍,比如《实用的通用Lisp》(可在线阅读)。
在Emacs Lisp中:
什么可以被引用?
列表和符号。
引用数字会得到数字本身的值:
'5
等同于 5
。
当你引用列表时会发生什么?
例如:
'(one two)
的求值结果为
(list 'one 'two)
,其求值结果为
(list (intern "one") (intern ("two")))
。
(intern "one")
创建了一个名为"one"的符号,并将其存储在“中央”哈希映射表中,因此每次你说 'one
时,就会在该中央哈希映射表中查找名为 "one"
的符号。
但是什么是符号?
例如,在面向对象语言(Java/Javascript/Python)中,符号可以表示为具有name
字段的对象,该字段是符号的名称,如上面的"one"
,并且数据和/或代码可以与该对象关联。
因此,在Python中,符号可以实现为:
class Symbol:
def __init__(self,name,code,value):
self.name=name
self.code=code
self.value=value
(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上的人们向我解释符号和引号。
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
'(+ 2 3)
),并通过给程序语义解释来解释为所创建的语言中的代码。在这种情况下,您需要使用引号来保留数据,否则它会在外部语言中被评估。This special form returns object, without evaluating it.
(quote (+ 1 2))
⇒ (+ 1 2)
(quote foo)
⇒ foo
'foo
⇒ foo
''foo
⇒ (quote foo)
'(quote foo)
⇒ (quote foo)
9.4 反引号
反引号构造允许您引用一个列表,但有选择地评估该列表的元素。在最简单的情况下,它与特殊形式 quote 相同(在前一节中描述;请参见引用)。例如,这两个形式产生相同的结果:
`(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)
当我们想要传递参数本身而不是传递参数的值时,我们使用引号。这主要涉及在使用列表、对和原子进行过程传递时,这些在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
Quote返回其参数的内部表示。在阅读了太多关于quote不做什么的解释后,我才恍然大悟。如果REPL在我引用函数名称时没有将它们转换为大写,我可能就不会明白了。
所以,普通的Lisp函数将其参数转换为内部表示,然后评估参数并应用函数。Quote将其参数转换为内部表示,然后只是返回该表示。从技术上讲,说quote表示“不要评估”是正确的,但当我试图理解它的时候,告诉我它不做什么是很令人沮丧的。我的烤面包机也不会评估Lisp函数;但这不是你解释烤面包机的方式。
'this 'is 'true
时,它只返回最后一个即TRUE的输出。即使在portacle中,我也得到相同的输出。 - Totorothis
、is
和true
,但你只能看到最后一个被返回的值。(this
、is
和true
是独立的语句) - Wezl'