何时引用Emacs Lisp中的符号

lul*_*lau 12 lisp emacs 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)
  ""
)
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不在列表中.这个表达式做了四件事:

  1. 创建一个新的利弊细胞.
  2. 将新cons单元的car field设置为3.
  3. 将新cons单元的cdr字段设置为前(并且仍为当前)值a(即复制a可变插槽的内容).
  4. 将变量槽设置为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也没有这种限制.


Dre*_*rew 8

到目前为止给出的其他答案一方面阐明了函数的使用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中),代码需要获取符号才能将其值设置为扩充列表.