我开始用Emacs Lisp学习编程。我对符号引用感到困惑。 例如:
(progn
(setq a '(1 2))
(prin1 a)
(add-to-list 'a 3)
(prin1 a)
(setcar a 4)
(prin1 a)
(push 5 a)
""
)
为什么“add-to-list”函数需要带引号的符号作为其第一个参数,而“setcar”和“push”函数不需要参数引用?
答案 0 :(得分:18)
这是一个表示符号a
及其(setq a '(1 2))
之后的值的图表。框是基本数据结构(符号和锥形),箭头是指针(其中一条数据引用另一个)。 (我正在简化一点。)
symbol cons cons
+-------+----------+ +------+------+ +------+------+
|name: |variable: | |car: |cdr: | |car: |cdr: |
| a | | | | 1 | | | | 2 | nil |
+-------+----|-----+ +------+--|---+ +------+------+
| ↑ | ↑
+-------------+ +-------+
表达式'(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 |
+-------+----|-----+ +------+--|---+ +------+--|---+ +------+------+
| ↑ | ↑ | ↑
+-------------+ +-------+ +-------+
对setcar
的调用不会创建任何新的数据结构,也不会对符号a
起作用,而是对其值,即当前car
的cons单元格起作用包含3. (setcar a 4)
之后,数据结构如下所示:
symbol cons cons cons
+-------+----------+ +------+--|---+ +------+------+ +------+------+
|name: |variable: | |car: |cdr: | |car: |cdr: | |car: |cdr: |
| a | | | | 4 | | | | 1 | | | | 2 | nil |
+-------+----|-----+ +------+--|---+ +------+--|---+ +------+------+
| ↑ | ↑ | ↑
+-------------+ +-------+ +-------+
push
是一个宏;在这里,(push 5 a)
相当于(set 'a (cons 5 a))
。
setq
和push
是宏(setq
是一种“特殊形式”,就我们这里所关注的而言,这意味着一个宏的定义是内置于解释器而不是在Lisp中提供)。宏收到他们的论点未评估,可以选择扩展或不扩展。 set
,setcar
和add-to-list
是函数,它们接收评估的参数。评估符号返回其变量槽的内容,例如,在最初的(setq a '(1 2))
之后,符号a
的值是其汽车包含1
的缺点单元格。
如果您仍然感到困惑,我建议您尝试使用(setq b a)
并亲自查看当您对b
采取行动时,哪些表达式会修改a
a
)和哪些(不对符号a
的值起作用的那些)。
答案 1 :(得分:13)
函数在执行之前评估它们的参数,因此当你需要传递一个实际的符号(例如指向一些数据结构的指针)时引用它,并且当它是一个变量值时不引用它。
add-to-list
执行其第一个参数的就地变异,因此需要带引号的符号。
push
不是一个函数,而是一个宏;这就是为什么它能够在没有评估的情况下接受不带引号的参数。内置表单,如setcar
,也没有这种限制。
答案 2 :(得分:8)
到目前为止给出的其他答案澄清了quote
的使用以及函数与宏和特殊之间的区别另一方面,表格。
但是,他们没有涉及问题的另一部分:为什么是add-to-list
? 为什么它需要第一个参数是 符号 ?这是一个单独的问题,它是否评估论证。这是add-to-list
。
可以想象 add-to-list
评估其args,并期望第一个arg的值为列表,然后将第二个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中),代码需要获取符号,以便将其值设置为扩充列表。