何时引用Emacs Lisp中的符号

时间:2011-12-29 16:10:43

标签: emacs lisp elisp

我开始用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”函数不需要参数引用?

3 个答案:

答案 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不在列表中。这个表达式做了四件事:

  1. 创建一个新的利弊单元。
  2. 将新cons单元的car field设置为3
  3. 将新cons单元的cdr字段设置为a的前(并且仍为当前)值(即复制a的变量槽的内容)。
  4. a的变量槽设置为新的cons单元格。
  5. 在该调用之后,涉及的数据结构如下所示:

     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))

    setqpush是宏(setq是一种“特殊形式”,就我们这里所关注的而言,这意味着一个宏的定义是内置于解释器而不是在Lisp中提供)。宏收到他们的论点未评估,可以选择扩展或不扩展。 setsetcaradd-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中),代码需要获取符号,以便将其值设置为扩充列表。