我开始用Emacs Lisp学习编程.我对符号引用感到困惑.例如:
(progn
(setq a '(1 2))
(prin1 a)
(add-to-list 'a 3)
(prin1 a)
(setcar a 4)
(prin1 a)
(push 5 a)
""
)
Run Code Online (Sandbox Code Playgroud)
为什么"add-to-list"函数需要带引号的符号作为其第一个参数,而"setcar"和"push"函数不需要参数引用?
Gil*_*il' 18
这是一个表示符号a及其后的值的图表(setq a '(1 2)).框是基本数据结构(符号和锥形),箭头是指针(其中一条数据引用另一个).(我正在简化一点.)
symbol cons cons
+-------+----------+ +------+------+ +------+------+
|name: |variable: | |car: |cdr: | |car: |cdr: |
| a | | | | 1 | | | | 2 | nil |
+-------+----|-----+ +------+--|---+ +------+------+
| ?? | ?
+-------------+ +-------+
Run Code Online (Sandbox Code Playgroud)
表达式'(1 2)在右侧构建了两个conses,它构成了一个两元素列表.表达式(setq a '(1 2))创建符号(a如果它不存在),然后使其"变量槽"(包含符号值的部分)指向新创建的列表.setq是一个内置的宏,(setq a '(1 2))是简写(set 'a '(1 2)).第一个参数set是要修改的符号,第二个参数是将符号的变量槽设置为的值.
(add-to-list 'a 3)相当于(set 'a (cons 3 a))这里,因为3不在列表中.这个表达式做了四件事:
3.a(即复制a可变插槽的内容).a新的cons单元格.在调用之后,涉及的数据结构如下所示:
symbol cons cons cons
+-------+----------+ +------+--|---+ +------+------+ +------+------+
|name: |variable: | |car: |cdr: | |car: |cdr: | |car: |cdr: |
| a | | | | 3 | | | | 1 | | | | 2 | nil |
+-------+----|-----+ +------+--|---+ +------+--|---+ +------+------+
| ?? | ? | ?
+-------------+ +-------+ +-------+
Run Code Online (Sandbox Code Playgroud)
调用setcar不会创建任何新的数据结构,也不会对符号起作用,a而是对其值进行操作,这是car当前包含3 的cons单元格.之后(setcar a 4),数据结构如下所示:
symbol cons cons cons
+-------+----------+ +------+--|---+ +------+------+ +------+------+
|name: |variable: | |car: |cdr: | |car: |cdr: | |car: |cdr: |
| a | | | | 4 | | | | 1 | | | | 2 | nil |
+-------+----|-----+ +------+--|---+ +------+--|---+ +------+------+
| ?? | ? | ?
+-------------+ +-------+ +-------+
Run Code Online (Sandbox Code Playgroud)
push是一个宏; 在这里,(push 5 a)相当于(set 'a (cons 5 a)).
setq并且push是宏(setq是一种"特殊形式",就我们这里所关注的而言,这意味着一个宏,其定义内置于解释器中,而不是在Lisp中提供).宏收到他们的论点未评估,可以选择扩展或不扩展.set,setcar并且add-to-list是接收其评估的参数的函数.评估符号返回其变量槽的内容,例如(setq a '(1 2)),在符号的初始值为a其汽车包含的cons单元格之后1.
如果您仍然感到困惑,我建议(setq b a)您自己尝试和查看哪些表达式b在您操作时会被修改a(对符号起作用的那些a)以及哪些表达式不会(对符号值起作用的表达式a) .
Vic*_*gin 13
函数在执行之前评估它们的参数,因此在需要传递实际符号(例如指向某些数据结构的指针)时引用,并且当它是变量值时不引用.
add-to-list 执行其第一个参数的就地变异,因此它需要一个带引号的符号.
push不是一个功能,而是一个宏; 这就是为什么它能够在没有评估的情况下接受不带引号的参数.内置形式,setcar也没有这种限制.
到目前为止给出的其他答案一方面阐明了函数的使用quote和差异,另一方面澄清了宏和特殊形式.
但是,他们没有涉及问题的另一部分:为什么会add-to-list这样?为什么它要求它的第一个参数是一个符号?这是一个单独的问题,它是否评估论证.这是设计背后的真正问题add-to-list.
人们可以设想的是add-to-list评价其指定参数时,和预期的第一个参数的值是一个列表,然后加入第二arg的值到该列表中作为一个元素和返回的结果(新的列表或相同的列表).这样你就(add-to-list foo 'shoe)可以将符号添加shoe到列表中,该值是foo- 比如(1 2 buckle)- ,给予(1 2 buckle shoe).
关键是这样的功能不是很有用.为什么?因为列表值不一定是可访问的.变量foo可能被认为是访问它的一种方式 - 它的"句柄"或"指针".但是函数返回的列表却不是这样.返回的列表可以由新的列表结构组成,通常没有(没有变量)指向该列表.函数add-to-list永远不会看到符号(变量)foo- 它无法知道它作为第一个参数接收的列表值是否被绑定foo.如果add-to-list以这种方式设计,那么您仍然需要将其返回的结果分配给列表变量.
IOW,add-to-list评估它的args,因为它是一个函数,但这并没有解释太多.它期望一个符号作为其第一个arg的值.并且它期望该变量(符号)的值为列表.它将第二个arg的值添加到列表中(可能更改列表结构),并将变量的值设置为该列表的第一个arg的值.
底线:它需要一个符号作为arg,因为它的作用是为该符号分配一个新值(新值是相同的列表值或相同的值,前面添加了新的列表元素).
是的,另一种方法是使用宏或特殊形式,如push.这是同样的想法:push想要一个符号作为它的第二个arg.不同之处在于push不评估其args,因此不需要引用符号.但在这两种情况下(在Emacs Lisp中),代码需要获取符号才能将其值设置为扩充列表.