2 Star 13 Fork 3

advanceflow/Elisp

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
12-变量.org 86.19 KB
一键复制 编辑 原始数据 按行查看 历史
advanceflow 提交于 2022-05-28 10:12 . finish s2

12 变量

变量是程序中用来代表值的名称。 在 Lisp 中,每个变量都由一个 Lisp 符号表示(参见符号)。 变量名称就是符号的名称,变量的值存储在符号的值 cell8 中。 请参阅符号组件。 在 Emacs Lisp 中,将符号用作变量与将其用作函数名无关。

如本手册前面所述,Lisp 程序主要由 Lisp 对象表示,其次是文本。 Lisp 程序的文本形式由构成程序的 Lisp 对象的读取语法给出。 因此,Lisp 程序中变量的文本形式是使用表示变量的符号的读取语法编写的。

12.1 全局变量

使用变量的最简单方法是全局。 这意味着该变量一次只有一个值,并且该值在整个 Lisp 系统中都有效(至少目前是这样)。 在您指定新值之前,该值一直有效。 当新值替换旧值时,变量中不会留下旧值的痕迹。

您可以使用 setq 为符号指定一个值。 例如,

(setq x '(a b))

给变量 x 赋值 (ab)。 注意 setq 是一种特殊形式(见特殊形式); 它不会评估它的第一个参数,即变量的名称,但它会评估第二个参数,即新值。

一旦变量有了值,您就可以通过使用符号本身作为表达式来引用它。 因此,

x ⇒ (a b)

假设上面显示的 setq 形式已经被执行。

如果您再次设置相同的变量,新值将替换旧值:

x
     ⇒ (a b)

(setq x 4)
     ⇒ 4

x
     ⇒ 4

12.2 永不改变的变量

在 Emacs Lisp 中,某些符号通常会对自己求值。 这些包括 nil 和 t,以及名称以“:”开头的任何符号(这些称为关键字)。 这些符号不能被反弹,也不能改变它们的值。 任何设置或绑定 nil 或 t 的尝试都表示设置常量错误。 对于关键字(名称以’:’开头的符号)也是如此,如果它被实习在标准 obarray 中,除了将这样的符号设置为自身不是错误。

nil ≡ 'nil
     ⇒ nil

(setq nil 500)
error→ Attempt to set constant symbol: nil
Function: keywordp object ¶

如果 object 是一个名称以 ‘:’ 开头的符号,则函数返回 t,并在标准 obarray 中实习,否则返回 nil。

这些常量与使用 defconst 特殊形式定义的常量根本不同(请参阅定义全局变量)。 defconst 形式用于通知人类读者您不打算更改变量的值,但如果您实际更改它,Emacs 不会引发错误。

由于各种实际原因,少量附加符号被设为只读。 其中包括 enable-multibyte-characters、most-positive-fixnum、most-negative-fixnum 和其他一些。 任何设置或绑定这些的尝试也表示设置常量错误。

12.3 局部变量

全局变量的值一直持续到被新值明确取代。 有时给变量一个局部值是有用的——这个值只在 Lisp 程序的特定部分内生效。 当一个变量有一个局部值时,我们说它是局部绑定到那个值的,并且它是一个局部变量。

例如,当一个函数被调用时,它的参数变量接收局部值,这是提供给函数调用的实际参数; 这些本地绑定在函数体内生效。 再举一个例子,let 特殊形式显式地为特定变量建立局部绑定,这些绑定只在 let 形式的主体内生效。

我们还谈到了全局绑定,这是(概念上)全局值被保存的地方。

建立本地绑定可以保存变量的先前值(或缺少值)。 我们说之前的值是阴影的。 全局值和局部值都可能被遮蔽。 如果本地绑定有效,则在本地变量上使用 setq 将指定的值存储在本地绑定中。 当该本地绑定不再有效时,先前隐藏的值(或缺少值)会返回。

一个变量一次可以有多个本地绑定(例如,如果有嵌套的 let 表单绑定该变量)。 当前绑定是实际生效的本地绑定。 它通过评估变量符号来确定返回的值,它是由 setq 作用的绑定。

在大多数情况下,您可以将当前绑定视为最内部的本地绑定,如果没有本地绑定,则可以将其视为全局绑定。 更准确地说,称为范围规则的规则确定本地绑定在程序中的哪个位置生效。 Emacs Lisp 中的默认作用域规则称为动态作用域,它简单地说明在程序执行的任何给定点的当前绑定是仍然存在的该变量最近创建的绑定。 有关动态范围的详细信息,以及称为词法范围的替代范围规则,请参阅变量绑定的范围规则。

特殊形式 let 和 let* 用于创建本地绑定:

Special Form: let (bindings…) forms… ¶

这种特殊形式为特定的一组变量设置本地绑定,由绑定指定,然后按文本顺序评估所有形式。 它的返回值是表单中最后一个表单的值。 let 设置的本地绑定仅在表单主体内有效。

每个绑定都是 (i) 一个符号,在这种情况下,该符号在本地绑定到 nil; 或 (ii) 形式列表 (symbol value-form),在这种情况下,符号本地绑定到评估 value-form 的结果。 如果省略 value-form,则使用 nil。

绑定中的所有值形式都按照它们出现的顺序在将任何符号绑定到它们之前进行评估。 下面是一个例子:z 绑定到 y 的旧值,即 2,而不是 y 的新值,即 1。

     (setq y 2)
	   ⇒ 2


     (let ((y 1)
	    (z y))
	(list y z))
	   ⇒ (1 2)

另一方面,绑定的顺序是未指定的:在以下示例中,可能会打印 1 或 2。

     (let ((x 1)
	    (x 2))
	(print x))

因此,避免在一个 let 表单中多次绑定一个变量。

Special Form: let* (bindings…) forms… ¶

这种特殊形式与 let 类似,但它在计算其局部值之后立即绑定每个变量,然后再计算下一个变量的局部值。 因此,绑定中的表达式可以引用以这种 let* 形式绑定的前面的符号。 比较下面的例子和上面的 let 例子。

     (setq y 2)
	   ⇒ 2


     (let* ((y 1)
	     (z y))    ; Use the just-established value of y.
	(list y z))
	   ⇒ (1 1)
Special Form: letrec (bindings…) forms… ¶

这种特殊形式类似于 let*,但在计算任何局部值之前绑定了所有变量。 然后将这些值分配给本地绑定的变量。 这仅在词法绑定生效时有用,并且您想要创建引用绑定的闭包,否则使用 let* 时这些绑定尚未生效。

例如,这是一个在运行一次后将自身从钩子中移除的闭包:

     (letrec ((hookfun (lambda ()
			  (message "Run once")
			  (remove-hook 'post-command-hook hookfun))))
	(add-hook 'post-command-hook hookfun))
Special Form: dlet (bindings…) forms… ¶

这种特殊的形式就像 let 一样,但是它动态地绑定了所有的变量。 这很少有用——您通常希望以词法方式绑定普通变量,并动态绑定特殊变量(即用 defvar 定义的变量),而这正是 let 所做的。

dlet 在与假定某些变量是动态绑定的旧代码交互时很有用(请参阅动态绑定),但对这些变量进行 defvar 是不切实际的。 dlet 将临时使绑定变量特殊,执行表单,然后再次使变量非特殊。

Special Form: named-let name bindings &rest body ¶

这种特殊形式是受 Scheme 语言启发的循环结构。 和 let 类似:在 bindings 中绑定变量,然后计算 body。 然而,named-let 也将 name 绑定到一个局部函数,其形式参数是绑定中的变量,其主体是 body。 这允许 body 通过调用 name 递归调用自身,其中传递给 name 的参数用作递归调用中绑定变量的新值。

对数字列表求和的循环示例:

     (named-let sum ((numbers '(1 2 3 4))
		      (running-sum 0))
	(if numbers
	    (sum (cdr numbers) (+ running-sum (car numbers)))
	  running-sum))
     ⇒ 10

在 body 的尾部位置对 name 的递归调用保证被优化为尾部调用,这意味着无论递归运行多深,它们都不会消耗任何额外的堆栈空间。 这样的递归调用将有效地跳转到循环的顶部,并为变量提供新值。

如果函数调用是最后完成的事情,则函数调用位于尾部位置,因此调用返回的值是 body 本身的值,就像上面对 sum 的递归调用中的情况一样。

以下是创建本地绑定的其他工具的完整列表:

函数调用(参见函数)。 宏调用(参见宏)。 条件案例(见错误)。

变量也可以具有缓冲区局部绑定(请参阅缓冲区局部变量); 一些变量具有终端本地绑定(请参阅多个终端)。 这些类型的绑定有点像普通的本地绑定,但它们是本地化的,具体取决于您在 Emacs 中的位置。

User Option: max-specpdl-size ¶

此变量定义了在 Emacs 发出错误信号(数据“变量绑定深度超过 max-specpdl-size”)之前允许的局部变量绑定和展开保护清理(请参阅从非本地退出清理)的总数限制。

这个限制,以及当它被超过时的相关错误,是 Lisp 避免对定义不明确的函数进行无限递归的一种方式。 max-lisp-eval-depth 提供了嵌套深度的另一个限制。 见评估。

默认值为 1600。进入 Lisp 调试器会增加该值,如果剩余空间很小,以确保调试器本身有执行空间。

12.4 当变量为空时

如果一个变量的符号有一个未赋值的单元格,我们就说这个变量是无效的(参见符号组件)。

在 Emacs Lisp 的默认动态范围规则下(请参阅变量绑定的范围规则),值单元格存储变量的当前(本地或全局)值。 请注意,未分配的值单元格与值单元格中的 nil 不同。 符号 nil 是一个 Lisp 对象,可以是变量的值,就像任何其他对象一样; 但它仍然是一个值。 如果变量为 void,则尝试评估该变量会发出 void-variable 错误信号,而不是返回值。

在可选的词法范围规则下,值单元仅保存变量的全局值——任何词法绑定结构之外的值。 当一个变量被词法绑定时,局部值由词法环境决定; 因此,即使变量符号的值单元未分配,变量也可以具有局部值。

Function: makunbound symbol ¶

该函数清空符号的值单元格,使变量无效。 它返回符号。

如果 symbol 具有动态局部绑定,makunbound 会使当前绑定无效,并且这种无效只会在局部绑定有效时持续。 之后,先前被遮蔽的局部或全局绑定被重新暴露; 那么变量将不再是无效的,除非重新暴露的绑定也是无效的。

以下是一些示例(假设动态绑定有效):



     (setq x 1)               ; Put a value in the global binding.
	   ⇒ 1
     (let ((x 2))             ; Locally bind it.
	(makunbound 'x)        ; Void the local binding.
	x)
     error→ Symbol's value as variable is void: x

     x                        ; The global binding is unchanged.
	   ⇒ 1

     (let ((x 2))             ; Locally bind it.
	(let ((x 3))           ; And again.
	  (makunbound 'x)      ; Void the innermost-local binding.
	  x))                  ; And refer: it’s void.
     error→ Symbol's value as variable is void: x


     (let ((x 2))
	(let ((x 3))
	  (makunbound 'x))     ; Void inner binding, then remove it.
	x)                     ; Now outer let binding is visible.
	   ⇒ 2
Function: boundp variable ¶

如果变量(符号)不为 void,则此函数返回 t,如果为 void,则返回 nil。

以下是一些示例(假设动态绑定有效):

     (boundp 'abracadabra)          ; Starts out void.
	   ⇒ nil

     (let ((abracadabra 5))         ; Locally bind it.
	(boundp 'abracadabra))
	   ⇒ t

     (boundp 'abracadabra)          ; Still globally void.
	   ⇒ nil

     (setq abracadabra 5)           ; Make it globally nonvoid.
	   ⇒ 5

     (boundp 'abracadabra)
	   ⇒ t

12.5 定义全局变量

变量定义是一种结构,它表明您打算将符号用作全局变量。 它使用下面记录的特殊形式 defvar 或 defconst。

变量定义有三个目的。 首先,它通知阅读代码的人该符号旨在以某种方式(作为变量)使用。 其次,它通知 Lisp 系统这一点,可选地提供一个初始值和一个文档字符串。 第三,它为 etags 等编程工具提供信息,使它们能够找到变量的定义位置。

defconst 和 defvar 之间的区别主要是意图问题,用于告知人类读者该值是否应该改变。 Emacs Lisp 实际上并不会阻止您更改使用 defconst 定义的变量的值。 这两种形式之间的一个显着区别是 defconst 无条件地初始化变量,而 defvar 仅在它最初为 void 时才对其进行初始化。

要定义可自定义的变量,您应该使用 defcustom(将 defvar 作为子例程调用)。 请参阅定义自定义变量。

Special Form: defvar symbol [value [doc-string]] ¶

这种特殊形式将符号定义为变量。 请注意,不评估符号; 要定义的符号应该以 defvar 形式显式出现。 该变量被标记为特殊,这意味着它应该始终是动态绑定的(请参阅变量绑定的范围规则)。

如果指定了 value,并且 symbol 为 void(即,它没有动态绑定的值;请参阅当变量为 Void 时),则计算 value 并将 symbol 设置为结果。 但如果 symbol 不是 void,则不会评估 value,并且 symbol 的值保持不变。 如果省略 value,则符号的值在任何情况下都不会改变。

请注意,指定一个值,即使是 nil,也会将变量永久标记为特殊。 而如果 value 被省略,则该变量仅在本地标记为特殊(即在当前词法范围内,或者如果在顶层,则为文件)。 这对于抑制字节编译警告很有用,请参阅编译器错误。

如果 symbol 在当前缓冲区中具有缓冲区本地绑定,则 defvar 作用于与缓冲区无关的默认值,而不是缓冲区本地绑定。 如果默认值为 void,它会设置默认值。 请参阅缓冲区局部变量。

如果 symbol 已经被词法绑定(例如,如果 defvar 形式出现在启用词法绑定的 let 形式中),则 defvar 设置动态值。 词法绑定在其绑定构造退出之前一直有效。 请参阅变量绑定的范围规则。

当您在 Emacs Lisp 模式下使用 CMx (eval-defun) 或 Cx Ce (eval-last-sexp) 评估顶级 defvar 表单时,这两个命令的一个特殊功能安排无条件设置变量,而不测试其是否价值是无效的。

如果提供了 doc-string 参数,它指定变量的文档字符串(存储在符号的 variable-documentation 属性中)。 请参阅文档。

这里有些例子。 这种形式定义了 foo 但没有初始化它:

  (defvar foo)
	   ⇒ foo

这个例子将 bar 的值初始化为 23,并给它一个文档字符串:

     (defvar bar 23
	"The normal weight of a bar.")
	   ⇒ bar

defvar 形式返回符号,但通常在文件的顶层使用它的值无关紧要。

有关在没有值的情况下使用 defvar 的更详细示例,请参阅本地 defvar 示例。

Special Form: defconst symbol value [doc-string]

这种特殊形式将符号定义为一个值并对其进行初始化。 它通知阅读您的代码的人符号具有标准全局值,在此处建立,用户或其他程序不应更改该值。 请注意,不评估符号; 要定义的符号必须显式出现在 defconst 中。

defconst 形式与 defvar 一样,将变量标记为特殊,这意味着它应该始终是动态绑定的(请参阅变量绑定的范围规则)。 此外,它会将变量标记为有风险的(请参阅文件局部变量)。

defconst 总是计算 value,并将 symbol 的值设置为结果。 如果 symbol 在当前缓冲区中确实具有缓冲区本地绑定,则 defconst 设置默认值,而不是缓冲区本地值。 (但您不应该为使用 defconst 定义的符号进行缓冲区本地绑定。)

使用 defconst 的一个例子是 Emacs 对 float-pi 的定义——数学常数 pi,任何人都不应该改变它(尽管印第安纳州立法机构有尝试)。 然而,正如第二种形式所示,defconst 只是建议性的。

  (defconst float-pi 3.141592653589793 "The value of Pi.")
	   ⇒ float-pi

  (setq float-pi 3)
	   ⇒ float-pi

  float-pi
	   ⇒ 3

警告:如果您使用 defconst 或 defvar 特殊形式,而变量具有局部绑定(使用 let 或函数参数),它将设置局部绑定而不是全局绑定。 这不是您通常想要的。 为了防止这种情况,在文件的顶层使用这些特殊形式,通常没有本地绑定生效,并确保在为变量进行本地绑定之前加载文件。

12.6 稳健定义变量的技巧

当您定义一个值为函数或函数列表的变量时,请分别使用以“-function”或“-functions”结尾的名称。

还有其他几种变量名称约定; 这是一个完整的列表:

‘…-hook’

该变量是一个普通的钩子(参见 Hooks)。

‘…-function’

值是一个函数。

‘…-functions’

该值是函数列表。

‘…-form’

该值是一种形式(一个表达式)。

‘…-forms’

该值是表单(表达式)的列表。

‘…-predicate’

该值是一个谓词——一个参数的函数,成功返回非零,失败返回零。

‘…-flag’

该值仅在它是否为零时才有意义。 由于这些变量通常最终会随着时间的推移获得更多的值,因此强烈建议不要使用此约定。

‘…-program’

该值是程序名称。

‘…-command’

该值是一个完整的 shell 命令。

‘…-switches’

该值指定命令的选项。

‘prefix--…’

该变量供内部使用,并在文件 prefix.el 中定义。 (2018 年之前贡献的 Emacs 代码可能遵循其他约定,这些约定正在逐步淘汰。)

‘…-internal’

该变量供内部使用,并在 C 代码中定义。 (2018 年之前贡献的 Emacs 代码可能遵循其他约定,这些约定正在逐步淘汰。)

定义变量时,请始终考虑是否应将其标记为安全或有风险; 请参阅文件局部变量。

在定义和初始化包含复杂值的变量时(例如其中包含绑定的键映射),最好将值的整个计算放入 defvar 中,如下所示:

(defvar my-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map "\C-c\C-a" 'my-command)
    …
    map)
  docstring)

这种方法有几个好处。 首先,如果用户在加载文件时退出,变量要么仍未初始化,要么已正确初始化,不会介于两者之间。 如果它仍然未初始化,重新加载文件将正确初始化它。 其次,变量初始化后重新加载文件不会改变它; 如果用户已经运行钩子来改变部分内容(例如,重新绑定键),这一点很重要。 第三,使用 CMx 评估 defvar 形式将完全重新初始化地图。

将这么多代码放在 defvar 形式中有一个缺点:它使文档字符串远离命名变量的行。 这是避免这种情况的安全方法:

(defvar my-mode-map nil
  docstring)
(unless my-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map "\C-c\C-a" 'my-command)
    …
    (setq my-mode-map map)))

这与将初始化放在 defvar 中具有所有相同的优点,除了您必须键入 CMx 两次,每个表单上一次,如果您确实要重新初始化变量。

12.7 访问变量值

引用变量的常用方法是编写命名它的符号。 请参阅符号形式。

有时,您可能希望引用仅在运行时确定的变量。 在这种情况下,您不能在程序文本中指定变量名称。 您可以使用符号值函数来提取值。

Function: symbol-value symbol ¶

此函数返回存储在符号值单元格中的值。 这是存储变量当前(动态)值的地方。 如果变量没有本地绑定,这只是它的全局值。 如果变量为 void,则会发出 void-variable 错误信号。

如果变量是词法绑定的,则 symbol-value 报告的值不一定与变量的词法值相同,这是由词法环境而不是符号的值单元格决定的。 请参阅变量绑定的范围规则。

     (setq abracadabra 5)
	   ⇒ 5

     (setq foo 9)
	   ⇒ 9


     ;; Here the symbol abracadabra
     ;;   is the symbol whose value is examined.
     (let ((abracadabra 'foo))
	(symbol-value 'abracadabra))
	   ⇒ foo


     ;; Here, the value of abracadabra,
     ;;   which is foo,
     ;;   is the symbol whose value is examined.
     (let ((abracadabra 'foo))
	(symbol-value abracadabra))
	   ⇒ 9


     (symbol-value 'abracadabra)
	   ⇒ 5

12.8 设置变量值

更改变量值的常用方法是使用特殊形式 setq。 当您需要在运行时计算变量的选择时,请使用函数集。

Special Form: setq [symbol form]… ¶

这种特殊形式是更改变量值的最常用方法。 每个符号都被赋予一个新值,这是对相应形式求值的结果。 符号的当前绑定已更改。

setq 不评估符号; 它设置您编写的符号。 我们说这个论点是自动引用的。 setq 中的“q”代表“引用”。

setq 形式的值是最后一个形式的值。

     (setq x (1+ 2))
	   ⇒ 3

     x                   ; x now has a global value.
	   ⇒ 3

     (let ((x 5))
	(setq x 6)        ; The local binding of x is set.
	x)
	   ⇒ 6

     x                   ; The global value is unchanged.
	   ⇒ 3

请注意,先计算第一种形式,然后设置第一个符号,然后计算第二种形式,然后设置第二个符号,依此类推:

  (setq x 10          ; Notice that x is set before
	    y (1+ x))     ;   the value of y is computed.
	   ⇒ 11
Function: set symbol value ¶

此函数将值放入符号的值单元格中。 因为它是一个函数而不是一个特殊的形式,所以为符号编写的表达式被求值以获得要设置的符号。 返回值是值。

当动态变量绑定生效(默认)时,set 与 setq 具有相同的效果,除了 set 计算其符号参数而 setq 不计算这一事实。 但是当一个变量被词法绑定时,set 会影响它的动态值,而 setq 会影响它的当前(词法)值。 请参阅变量绑定的范围规则。

     (set one 1)
     error→ Symbol's value as variable is void: one

     (set 'one 1)
	   ⇒ 1

     (set 'two 'one)
	   ⇒ one

     (set two 2)         ; two evaluates to symbol one.
	   ⇒ 2

     one                 ; So it is one that was set.
	   ⇒ 2
     (let ((one 1))      ; This binding of one is set,
	(set 'one 3)      ;   not the global value.
	one)
	   ⇒ 3

     one
	   ⇒ 2

如果 symbol 实际上不是一个符号,则会发出错误类型参数错误的信号。

(set '(x y) 'z)
error→ Wrong type argument: symbolp, (x y)

12.9 当变量改变时运行函数。

当变量改变它的值时,采取一些行动有时是有用的。 变量观察点工具提供了这样做的方法。 此功能的一些可能用途包括使显示与变量设置保持同步,并调用调试器以跟踪对变量的意外更改(请参阅在修改变量时进入调试器)。

以下函数可用于操作和查询变量的监视函数。

Function: add-variable-watcher symbol watch-function ¶

此函数安排在修改符号时调用 watch-function。 通过别名进行修改(请参阅变量别名)将具有相同的效果。

watch-function 将在更改 symbol 的值之前被调用,带有 4 个参数:symbol、newval、operation 和 where。 symbol 是被改变的变量。 newval 是将更改为的值。 (旧值可作为 symbol 的值用于 watch-function,因为它尚未更改为 newval。) operation 是表示更改类型的符号,其中之一是:set、let、unlet、makunbound 或 defvaralias。 如果变量的缓冲区局部值正在更改,则 where 是缓冲区,否则为 nil。

Function: remove-variable-watcher symbol watch-function ¶

此函数从符号的观察者列表中删除观察函数。

Function: get-variable-watchers symbol ¶

此函数返回符号的活动观察函数列表。

12.9.1 限制

有几种方法可以在不触发观察点的情况下修改(或至少看起来已修改)变量。

由于观察点附加到符号,因此该机制不会捕获对包含在变量中的对象的修改(例如,通过列表修改函数,请参阅修改现有列表结构)。

此外,C 代码可以绕过观察点机制直接修改变量的值。

此功能的一个小限制(同样因为它针对符号)是只能观察动态范围的变量。 这没有什么困难,因为可以通过检查变量范围内的代码轻松发现对词法变量的修改(与动态变量不同,动态变量可以由任何代码修改,请参阅变量绑定的范围规则)。

12.10 变量绑定的作用域规则

当您为变量创建局部绑定时,该绑定仅在程序的有限部分内生效(请参阅局部变量)。 本节准确描述了这意味着什么。

每个本地绑定都有一定的范围和程度。 范围是指在文本源代码中可以访问绑定的位置。 范围是指当程序执行时,绑定存在的时间。

默认情况下,Emacs 创建的本地绑定是动态绑定。 这种绑定具有动态范围,这意味着程序的任何部分都可以潜在地访问变量绑定。 它还具有动态范围,这意味着绑定仅在绑定构造(例如 let 表单的主体)正在执行时才持续。

Emacs 可以选择创建词法绑定。 词法绑定具有词法范围,这意味着对变量的任何引用都必须以文本形式位于绑定结构中9。 它还具有不确定的范围,这意味着在某些情况下,即使在绑定构造完成执行之后,绑定也可以通过称为闭包的特殊对象继续存在。

以下小节更详细地描述了动态绑定和词法绑定,以及如何在 Emacs Lisp 程序中启用词法绑定。

12.10.1 动态绑定

默认情况下,Emacs 进行的局部变量绑定是动态绑定。 当一个变量被动态绑定时,它在 Lisp 程序执行中的任何时候的当前绑定只是该符号最近创建的动态局部绑定,或者如果没有这样的局部绑定,则为全局绑定。

动态绑定具有动态范围和范围,如以下示例所示:

(defvar x -99)  ; x receives an initial value of -99.

(defun getx ()
  x)            ; x is used free in this function.

(let ((x 1))    ; x is dynamically bound.
  (getx))
     ⇒ 1

;; After the let form finishes, x reverts to its
;; previous value, which is -99.

(getx)
     ⇒ -99

函数 getx 引用 x。 这是一个自由引用,因为在该 defun 构造本身中没有对 x 的绑定。 当我们在 x 被(动态)绑定的 let 形式中调用 getx 时,它会检索本地值(即 1)。 但是当我们在 let 表单之外调用 getx 时,它会检索全局值(即 -99)。

这是另一个示例,它说明了使用 setq 设置动态绑定变量:

(defvar x -99)      ; x receives an initial value of -99.

(defun addx ()
  (setq x (1+ x)))  ; Add 1 to x and return its new value.

(let ((x 1))
  (addx)
  (addx))
     ⇒ 3           ; The two addx calls add to x twice.

;; After the let form finishes, x reverts to its
;; previous value, which is -99.

(addx)
     ⇒ -98

动态绑定在 Emacs Lisp 中以一种简单的方式实现。 每个符号都有一个值单元格,它指定了它的当前动态值(或没有值)。 请参阅符号组件。 当一个符号被赋予动态本地绑定时,Emacs 将值单元的内容(或不存在)记录在堆栈中,并将新的本地值存储在值单元中。 当绑定结构完成执行时,Emacs 将旧值从堆栈中弹出,并将其放入值单元格中。

请注意,当使用动态绑定的代码被本地编译时,本地编译器将不会执行任何 Lisp 特定的优化。

12.10.2 正确使用动态绑定

动态绑定是一项强大的功能,因为它允许程序引用未在其本地文本范围内定义的变量。 但是,如果不加约束地使用,这也会使程序难以理解。 有两种干净的方法可以使用此技术:

如果变量没有全局定义,则仅在绑定构造中将其用作局部变量,例如绑定变量的 let 表单的主体。 如果在整个程序中始终遵循此约定,则变量的值将不会影响,也不会受到程序其他地方对相同变量符号的任何使用的影响。 否则,使用 defvar、defconst(请参阅定义全局变量)或 defcustom(请参阅定义自定义变量)定义变量。 通常,定义应该位于 Emacs Lisp 文件的顶层。 它应尽可能包含一个解释变量含义和用途的文档字符串。 您还应该选择变量的名称以避免名称冲突(请参阅 Emacs Lisp 编码约定)。

然后你可以在程序的任何地方绑定变量,可靠地知道效果会是什么。 无论您在哪里遇到变量,都可以很容易地返回定义,例如,通过 Ch v 命令(假设变量定义已加载到 Emacs 中)。 请参阅 GNU Emacs 手册中的名称帮助。

例如,通常将本地绑定用于可自定义的变量,例如 case-fold-search:

     (defun search-for-abc ()
	"Search for the string \"abc\", ignoring case differences."
	(let ((case-fold-search t))
	  (re-search-forward "abc")))

12.10.3 词法绑定

词法绑定作为可选功能被引入 Emacs,在 24.1 版本中。 我们预计它的重要性会随着时间的推移而增加。 词法绑定为优化提供了更多机会,因此使用它的程序可能会在未来的 Emacs 版本中运行得更快。 词法绑定也更兼容并发,它是在 Emacs 26.1 版本中添加的。

词法绑定变量具有词法范围,这意味着对该变量的任何引用都必须以文本形式位于绑定构造中。 这是一个示例(请参阅使用词法绑定,了解如何实际启用词法绑定):

(let ((x 1))    ; x is lexically bound.
  (+ x 3))
     ⇒ 4

(defun getx ()
  x)            ; x is used free in this function.

(let ((x 1))    ; x is lexically bound.
  (getx))
error→ Symbol's value as variable is void: x

这里,变量 x 没有全局值。 当它被词汇绑定在一个 let 形式中时,它可以在该 let 形式的文本范围内使用。 但它不能在从 let 形式调用的 getx 函数中使用,因为 getx 的函数定义发生在 let 形式本身之外。

以下是词法绑定的工作原理。 每个绑定构造定义一个词法环境,指定绑定在构造内的变量及其本地值。 当 Lisp 求值器想要一个变量的当前值时,它首先在词法环境中查找; 如果没有在其中指定变量,它会在符号的值单元格中查找,其中存储了动态值。

(在内部,词法环境是一个符号值对的列表,列表中的最后一个元素是符号 t 而不是一个 cons 单元格。这样的列表可以作为第二个参数传递给 eval 函数,以便指定评估表单的词法环境。请参阅 Eval。然而,大多数 Emacs Lisp 程序不应该以这种方式直接与词法环境交互;只有专门的程序,如调试器。)

词法绑定有无限的范围。 即使在绑定结构完成执行之后,它的词法环境也可以“保留”在称为闭包的 Lisp 对象中。 当您定义启用了词法绑定的命名或匿名函数时,将创建一个闭包。 有关详细信息,请参阅闭包。

当闭包作为函数调用时,其定义中的任何词法变量引用都使用保留的词法环境。 这是一个例子:

(defvar my-ticker nil)   ; We will use this dynamically bound
			   ; variable to store a closure.

(let ((x 0))             ; x is lexically bound.
  (setq my-ticker (lambda ()
		      (setq x (1+ x)))))
    ⇒ (closure ((x . 0) t) ()
	    (setq x (1+ x)))

(funcall my-ticker)
    ⇒ 1

(funcall my-ticker)
    ⇒ 2

(funcall my-ticker)
    ⇒ 3

x                        ; Note that x has no global value.
error→ Symbol's value as variable is void: x

let 绑定定义了一个词法环境,其中变量 x 本地绑定到 0。在这个绑定构造中,我们定义了一个 lambda 表达式,它将 x 递增 1 并返回递增后的值。 这个 lambda 表达式会自动变成一个闭包,即使在 let 绑定结构退出之后,词法环境仍然存在。 每次我们评估闭包时,它都会增加 x,使用 x 在该词法环境中的绑定。

请注意,与绑定到符号对象本身的动态变量不同,词法变量和符号之间的关系仅存在于解释器(或编译器)中。 因此,接受符号参数的函数(如符号值、boundp 和集合)只能检索或修改变量的动态绑定(即,其符号值单元格的内容)。

12.10.4 使用词法绑定

在加载 Emacs Lisp 文件或评估 Lisp 缓冲区时,如果缓冲区局部变量 lexical-binding 为非 nil,则启用词法绑定:

Variable: lexical-binding ¶

如果这个缓冲区局部变量不为 nil,Emacs Lisp 文件和缓冲区将使用词法绑定而不是动态绑定进行评估。 (但是,特殊变量仍然是动态绑定的;见下文。)如果为 nil,则动态绑定用于所有局部变量。 此变量通常为整个 Emacs Lisp 文件设置为文件局部变量(请参阅文件局部变量)。 请注意,与其他此类变量不同,此变量必须在文件的第一行中设置。

当使用 eval 调用直接评估 Emacs Lisp 代码时,如果 eval 的词法参数不为零,则启用词法绑定。 见评估。

在 Lisp Interaction 和 IELM 模式下也启用了词法绑定,用于 scratchielm 缓冲区,以及通过 M-: (eval-expression) 评估表达式以及处理 –eval 命令行选项时Emacs(参见 The GNU Emacs Manual 中的 Action Arguments)和 emacsclient(参见 The GNU Emacs Manual 中的 emacsclient Options)。

即使启用了词法绑定,某些变量仍将继续被动态绑定。 这些被称为特殊变量。 使用 defvar、defcustom 或 defconst 定义的每个变量都是特殊变量(请参阅定义全局变量)。 所有其他变量都受词法绑定。

使用不带值的 defvar,可以将变量动态绑定到一个文件中,或者仅在文件的一部分中,同时仍以词法方式将其绑定到其他地方。 例如:

 (let (_)
   (defvar x)      ; Let-bindings of x will be dynamic within this let.
   (let ((x -99))  ; This is a dynamic binding of x.
     (defun get-dynamic-x ()
	x)))

 (let ((x 'lexical)) ; This is a lexical binding of x.
   (defun get-lexical-x ()
     x))

 (let (_)
   (defvar x)
   (let ((x 'dynamic))
     (list (get-lexical-x)
	    (get-dynamic-x))))
     ⇒ (lexical dynamic)
Function: special-variable-p symbol ¶

如果 symbol 是特殊变量(即,它具有 defvar、defcustom 或 defconst 变量定义),则此函数返回非 nil。 否则,返回值为 nil。

请注意,由于这是一个函数,它只能为永久特殊的变量返回非 nil,但不能为仅在当前词法范围内特殊的变量返回非 nil。

不支持在函数中使用特殊变量作为形式参数。

12.10.5 转换为词法绑定

将 Emacs Lisp 程序转换为词法绑定很容易。 首先,在 Emacs Lisp 源文件的标题行中添加 lexical-binding to t 的文件局部变量设置(请参阅文件局部变量)。 其次,检查程序中每个需要动态绑定的变量是否都有一个变量定义,以免无意中被词法绑定。

找出哪些变量需要变量定义的一种简单方法是对源文件进行字节编译。 请参阅字节编译。 如果在 let 形式之外使用了非特殊变量,字节编译器将警告对自由变量的引用或赋值。 如果非特殊变量被绑定但未在 let 形式中使用,字节编译器将警告未使用的词法变量。 如果您使用特殊变量作为函数参数,字节编译器也会发出警告。

关于对自由变量的引用或赋值的警告通常是一个明确的信号,表明该变量应标记为动态范围,因此您需要在第一次使用该变量之前添加适当的 defvar。

关于未使用变量的警告可能是一个很好的暗示,表明该变量是动态范围的(因为它实际上被使用,但在另一个函数中),但它也可能表明该变量实际上根本没有使用并且可以简单地被删除。 因此,您需要找出它是哪种情况,并在此基础上添加一个 defvar 或完全删除该变量。 如果删除是不可能或不可取的(通常是因为它是一个正式参数并且我们不能或不想更改所有调用者),您还可以在变量名称中添加前导下划线以向编译器表明此是一个已知不会使用的变量。) 跨文件变量检查

注意:这是一项实验性功能,可能会更改或消失,恕不另行通知。

字节编译器还可以警告其他 Emacs Lisp 文件中特殊的词法变量,通常表明缺少 defvar 声明。 这种有用但有些专业的检查需要三个步骤:

字节编译所有可能感兴趣的特殊变量声明的文件,环境变量 EMACS_GENERATE_DYNVARS 设置为非空字符串。 这些通常是同一个包或相关包或 Emacs 子系统中的所有文件。 该过程将为每个已编译的 Emacs Lisp 文件生成一个名称以 .dynvars 结尾的文件。 将 .dynvars 文件连接成一个文件。 字节编译需要检查的文件,这次将环境变量 EMACS_DYNVARS_FILE 设置为在步骤 2 中创建的聚合文件的名称。

下面是一个示例,说明如何做到这一点,假设 Unix shell 和 make 用于字节编译:

$ rm *.elc                                # force recompilation
$ EMACS_GENERATE_DYNVARS=1 make           # generate .dynvars
$ cat *.dynvars > ~/my-dynvars            # combine .dynvars
$ rm *.elc                                # force recompilation
$ EMACS_DYNVARS_FILE=~/my-dynvars make    # perform checks

12.11 缓冲区局部变量

全局和局部变量绑定在大多数编程语言中都以一种或另一种形式存在。 然而,Emacs 也支持其他不常见的变量绑定,例如缓冲区本地绑定,它只适用于一个缓冲区。 在不同的缓冲区中为变量设置不同的值是一种重要的定制方法。 (变量也可以具有每个终端本地的绑定。请参阅多个终端。)

12.11.1 缓冲区局部变量简介

缓冲区局部变量具有与特定缓冲区关联的缓冲区局部绑定。 当该缓冲区为当前时,绑定生效; 否则,它不会生效。 如果在缓冲区本地绑定生效时设置变量,则新值将进入该绑定,因此其其他绑定保持不变。 这意味着更改仅在您进行更改的缓冲区中可见。

变量的普通绑定,不与任何特定缓冲区关联,称为默认绑定。 在大多数情况下,这是全局绑定。

变量可以在某些缓冲区中具有缓冲区本地绑定,但在其他缓冲区中则不能。 默认绑定由没有自己的变量绑定的所有缓冲区共享。 (这包括所有新创建的缓冲区。)如果将变量设置在没有缓冲区本地绑定的缓冲区中,则会设置默认绑定,因此新值在所有看到默认值的缓冲区中可见捆绑。

缓冲区局部绑定最常见的用途是主要模式更改控制命令行为的变量。 例如,C 模式和 Lisp 模式都设置变量paragraph-start 来指定只有空行分隔段落。 他们通过在被放入 C 模式或 Lisp 模式的缓冲区中使变量缓冲区本地化,然后将其设置为该模式的新值来做到这一点。 请参阅主要模式。

进行缓冲区本地绑定的常用方法是使用 make-local-variable,这是主要模式命令通常使用的。 这仅影响当前缓冲区; 所有其他缓冲区(包括尚未创建的缓冲区)将继续共享默认值,除非它们被明确地赋予自己的缓冲区本地绑定。

更强大的操作是通过调用 make-variable-buffer-local 将变量标记为自动缓冲区本地。 您可以将其视为在所有缓冲区中使变量成为本地变量,即使是那些尚未创建的缓冲区。 更准确地说,效果是自动设置变量使变量成为当前缓冲区的本地变量,如果它还不是这样的话。 所有缓冲区一开始都像往常一样共享变量的默认值,但设置变量会为当前缓冲区创建一个缓冲区本地绑定。 新值存储在缓冲区本地绑定中,而默认绑定保持不变。 这意味着不能在任何缓冲区中使用 setq 更改默认值; 改变它的唯一方法是使用 setq-default。

警告:当一个变量在一个或多个缓冲区中具有缓冲区本地绑定时,让重新绑定当前有效的绑定。 例如,如果当前缓冲区有一个缓冲区本地值,那么 let 临时重新绑定它。 如果没有缓冲区本地绑定生效,让重新绑定默认值。 如果在 let 内部,您然后更改为不同的当前缓冲区,其中不同的绑定有效,您将不会再看到 let 绑定。 如果您在另一个缓冲区中退出 let ,您将不会看到解除绑定发生(尽管它会正确发生)。 下面是一个例子来说明:



(setq foo 'g)
(set-buffer "a")
(make-local-variable 'foo)

(setq foo 'a)
(let ((foo 'temp))
  ;; foo ⇒ 'temp  ; let binding in buffer ‘a’
  (set-buffer "b")
  ;; foo ⇒ 'g     ; the global value since foo is not local in ‘b’
  body…)

foo ⇒ 'g        ; exiting restored the local value in buffer ‘a’,
		   ; but we don’t see that in buffer ‘b’

(set-buffer "a") ; verify the local value was restored
foo ⇒ 'a

请注意,正文中对 foo 的引用访问缓冲区“b”的缓冲区本地绑定。

当文件指定局部变量值时,当您访问该文件时,这些值将成为缓冲区局部值。 请参阅 GNU Emacs 手册中的文件变量。

不能将缓冲区局部变量设为终端局部(请参阅多个终端)。

12.11.2 创建和删除缓冲区本地绑定

Command: make-local-variable variable ¶

此函数在当前缓冲区中为变量(符号)创建缓冲区本地绑定。 其他缓冲区不受影响。 返回的值是可变的。

变量的缓冲区局部值与以前的值变量相同。 如果变量是无效的,它仍然是无效的。



     ;; In buffer ‘b1’:
     (setq foo 5)                ; Affects all buffers.
	   ⇒ 5

     (make-local-variable 'foo)  ; Now it is local in ‘b1’.
	   ⇒ foo

     foo                         ; That did not change
	   ⇒ 5                   ;   the value.

     (setq foo 6)                ; Change the value
	   ⇒ 6                   ;   in ‘b1’.

     foo
	   ⇒ 6


     ;; In buffer ‘b2’, the value hasn’t changed.
     (with-current-buffer "b2"
	foo)
	   ⇒ 5

在该变量的 let 绑定中使变量局部缓冲区无法可靠地工作,除非您执行此操作的缓冲区在进入或退出 let 时不是当前的。 这是因为 let 不区分不同类型的绑定; 它只知道绑定是针对哪个变量的。

将常量或只读变量设置为缓冲区本地是错误的。 请参阅永不改变的变量。

如果变量是终端本地的(请参阅多个终端),则此函数会发出错误信号。 此类变量也不能具有缓冲区本地绑定。

警告:不要对钩子变量使用 make-local-variable。 如果您使用本地参数来添加挂钩或删除挂钩,则挂钩变量会根据需要自动设置为缓冲区本地。

Macro: setq-local &rest pairs ¶

对是变量和值对的列表。 这个宏在当前缓冲区中为每个变量创建一个缓冲区局部绑定,并给它们一个缓冲区局部值。 这相当于为每个变量调用 make-local-variable 后跟 setq。 变量应该是不带引号的符号。

  (setq-local var1 "value1"
		  var2 "value2")
Command: make-variable-buffer-local variable ¶

此函数自动将变量(符号)标记为缓冲区本地,以便任何后续设置它的尝试都将使其成为当时当前缓冲区的本地。 与经常混淆的 make-local-variable 不同,这无法撤消,并且会影响变量在所有缓冲区中的行为。

此功能的一个特殊问题是绑定变量(使用 let 或其他绑定结构)不会为其创建缓冲区本地绑定。 仅设置变量(使用 set 或 setq),而变量没有在当前缓冲区中创建的 let 样式绑定,这样做。

如果变量没有默认值,则调用此命令将给它一个默认值 nil。 如果变量已经具有默认值,则该值保持不变。 随后在变量上调用 makunbound 将产生一个 void 缓冲区局部值,并且不影响默认值。

返回的值是可变的。

将常量或只读变量设置为缓冲区本地是错误的。 请参阅永不改变的变量。

警告:不要假设您应该对用户选项变量使用 make-variable-buffer-local,因为用户可能希望在不同的缓冲区中以不同的方式自定义它们。 用户可以根据需要将任何变量设为本地变量。 最好把选择权留给他们。

使用 make-variable-buffer-local 的时候,关键是没有两个缓冲区共享相同的绑定。 例如,当一个变量在 Lisp 程序中用于内部目的时,它依赖于在单独的缓冲区中具有单独的值,那么使用 make-variable-buffer-local 可能是最好的解决方案。

Macro: defvar-local variable value &optional docstring ¶

该宏将变量定义为具有初始值和文档字符串的变量,并将其标记为自动缓冲区本地。 它相当于调用 defvar 后跟 make-variable-buffer-local。 变量应该是一个不带引号的符号。

Function: local-variable-p variable &optional buffer ¶

如果变量在缓冲区缓冲区(默认为当前缓冲区)中是缓冲区局部变量,则返回 t; 否则,无。

Function: local-variable-if-set-p variable &optional buffer ¶

如果变量在缓冲区缓冲区中具有缓冲区本地值,或者自动为缓冲区本地,则返回 t。 否则,它返回零。 如果省略或为零,则缓冲区默认为当前缓冲区。

Function: buffer-local-value variable buffer ¶

此函数返回缓冲区缓冲区中变量(符号)的缓冲区本地绑定。 如果变量在缓冲区缓冲区中没有缓冲区局部绑定,则返回变量的默认值(请参阅缓冲区局部变量的默认值)。

Function: buffer-local-boundp variable buffer ¶

如果缓冲区缓冲区中存在变量(符号)的缓冲区局部绑定,或者变量具有全局绑定,则返回非零。

Function: buffer-local-variables &optional buffer ¶

此函数返回一个列表,描述缓冲区缓冲区中的缓冲区局部变量。 (如果省略 buffer,则使用当前缓冲区。)通常,每个列表元素的格式为 (sym . val),其中 sym 是缓冲区局部变量(符号),val 是其缓冲区局部值。 但是当一个变量在缓冲区中的缓冲区局部绑定为 void 时,它的列表元素就是 sym。

   (make-local-variable 'foobar)
   (makunbound 'foobar)
   (make-local-variable 'bind-me)
   (setq bind-me 69)

   (setq lcl (buffer-local-variables))
	  ;; First, built-in variables local in all buffers:
   ⇒ ((mark-active . nil)
	  (buffer-undo-list . nil)
	  (mode-name . "Fundamental")
	  …

	  ;; Next, non-built-in buffer-local variables.
	  ;; This one is buffer-local and void:
	  foobar
	  ;; This one is buffer-local and nonvoid:
	  (bind-me . 69))

请注意,将新值存储到此列表中 cons 单元的 CDR 中不会更改变量的缓冲区本地值。

Command: kill-local-variable variable ¶

此函数删除当前缓冲区中变量(符号)的缓冲区本地绑定(如果有)。 结果,变量的默认绑定在此缓冲区中变得可见。 这通常会导致变量的值发生变化,因为默认值通常与刚刚消除的缓冲区局部值不同。

如果你杀死一个变量的缓冲区本地绑定,该绑定在设置时会自动变为缓冲区本地,这会使默认值在当前缓冲区中可见。 但是,如果您再次设置该变量,则会再次为其创建缓冲区本地绑定。

kill-local-variable 返回变量。

这个函数是一个命令,因为有时交互式地杀死一个缓冲区局部变量很有用,就像交互式地创建缓冲区局部变量一样有用。

Function: kill-all-local-variables ¶

此函数消除了当前缓冲区的所有缓冲区局部变量绑定,除了标记为永久的变量和具有非零永久局部钩子属性的局部钩子函数(请参阅设置钩子)。 结果,缓冲区将看到大多数变量的默认值。

此函数还重置与缓冲区有关的某些其他信息:它将本地键映射设置为 nil,将语法表设置为 (standard-syntax-table) 的值,将案例表设置为 (standard-case-table),并将缩写table 到 basic-mode-abbrev-table 的值。

这个函数做的第一件事就是运行普通的钩子 change-major-mode-hook(见下文)。

每个主模式命令都以调用此函数开始,该函数具有切换到基本模式的效果,并擦除之前主模式的大部分效果。 为确保其发挥作用,不应将主要模式设置的变量标记为永久。

kill-all-local-variables 返回 nil。

Variable: change-major-mode-hook ¶

函数 kill-all-local-variables 在执行其他任何操作之前运行这个普通的钩子。 如果用户切换到不同的主要模式,这为主要模式提供了一种安排特殊操作的方法。 如果用户更改主要模式,它对于应该忘记的缓冲区特定的次要模式也很有用。

为获得最佳效果,请将此变量设置为缓冲区本地,以便在完成工作后它会消失,并且不会干扰后续的主要模式。 请参阅挂钩。

如果变量名(符号)具有非 nil 的永久局部属性,则缓冲区局部变量是永久的。 这些变量不受 kill-all-local-variables 的影响,因此它们的本地绑定不会通过更改主要模式来清除。 永久本地变量适用于与文件来自何处或如何保存文件有关的数据,而不是与如何编辑内容有关的数据。

12.11.3 缓冲区局部变量的默认值

具有缓冲区局部绑定的变量的全局值也称为默认值,因为它是在当前缓冲区和选定帧都没有自己的变量绑定时生效的值。

无论当前缓冲区是否具有缓冲区本地绑定,函数 default-value 和 setq-default 都可以访问和更改变量的默认值。 例如,您可以使用 setq-default 更改大多数缓冲区的默认段落开始设置; 即使您在 C 或 Lisp 模式的缓冲区中,这也可以工作,该缓冲区具有该变量的缓冲区本地值。

特殊形式的 defvar 和 defconst 也设置默认值(如果它们设置了变量),而不是任何缓冲区本地值。

Function: default-value symbol ¶

此函数返回符号的默认值。 这是在没有此变量自己的值的缓冲区和帧中看到的值。 如果 symbol 不是缓冲区本地的,则这等效于 symbol-value(请参阅访问变量值)。

Function: default-boundp symbol ¶

函数 default-boundp 告诉您符号的默认值是否为非空值。 如果 (default-boundp ‘foo) 返回 nil,则 (default-value ‘foo) 会出错。

default-boundp 对应于默认值,就像 boundp 对应于符号值一样。

Special Form: setq-default [symbol form]… ¶

这种特殊形式为每个符号赋予了一个新的默认值,这是对相应形式求值的结果。 它不评估符号,但评估形式。 setq-default 形式的值是最后一个形式的值。

如果符号不是当前缓冲区的缓冲区本地,并且没有自动标记为缓冲区本地,则 setq-default 与 setq 具有相同的效果。 如果符号对于当前缓冲区是缓冲区本地的,那么这会更改其他缓冲区将看到的值(只要它们没有缓冲区本地值),但不会更改当前缓冲区看到的值。

  ;; In buffer ‘foo’:
  (make-local-variable 'buffer-local)
	   ⇒ buffer-local

  (setq buffer-local 'value-in-foo)
	   ⇒ value-in-foo

  (setq-default buffer-local 'new-default)
	   ⇒ new-default

  buffer-local
	   ⇒ value-in-foo

  (default-value 'buffer-local)
	   ⇒ new-default


  ;; In (the new) buffer ‘bar’:
  buffer-local
	   ⇒ new-default

  (default-value 'buffer-local)
	   ⇒ new-default

  (setq buffer-local 'another-default)
	   ⇒ another-default

  (default-value 'buffer-local)
	   ⇒ another-default


  ;; Back in buffer ‘foo’:
  buffer-local
	   ⇒ value-in-foo
  (default-value 'buffer-local)
	   ⇒ another-default
Function: set-default symbol value ¶

这个函数类似于 setq-default,除了 symbol 是一个普通的评估参数。

  (set-default (car '(a b c)) 23)
	   ⇒ 23

  (default-value 'a)
	   ⇒ 23

变量可以绑定(参见局部变量)到一个值。 这使得它的全局值被绑定所遮蔽; 然后 default-value 将返回该绑定的值,而不是全局值,并且 set-default 将被阻止设置全局值(它将更改 let-bound 值)。 以下两个函数允许引用全局值,即使它被 let-binding 遮蔽。

Function: default-toplevel-value symbol ¶

此函数返回符号的顶级默认值,这是它在任何 let 绑定之外的值。

     (defvar variable 'global-value)
	  ⇒ variable

     (let ((variable 'let-binding))
	(default-value 'variable))
	  ⇒ let-binding

     (let ((variable 'let-binding))
	(default-toplevel-value 'variable))
	  ⇒ global-value
Function: set-default-toplevel-value symbol value ¶

此函数将符号的顶级默认值设置为指定值。 当您想要设置 symbol 的全局值时,无论您的代码是否在 symbol 的 let-binding 上下文中运行,这都会派上用场。

12.12 文件局部变量

文件可以指定局部变量值; Emacs 使用这些来为访问该文件的缓冲区中的那些变量创建缓冲区本地绑定。 有关文件局部变量的基本信息,请参阅 GNU Emacs 手册中的文件中的局部变量。 本节介绍影响文件局部变量处理方式的函数和变量。

如果文件局部变量可以指定稍后调用的任意函数或 Lisp 表达式,则访问文件可能会接管您的 Emacs。 Emacs 通过仅自动设置那些指定值已知是安全的文件局部变量来防止这种情况发生。 只有在用户同意的情况下,才会设置其他文件局部变量。

为了更加安全,当 Emacs 读取文件局部变量时, read-circle 临时绑定为 nil(请参阅输入函数)。 这可以防止 Lisp 阅读器识别循环和共享的 Lisp 结构(请参阅循环对象的读取语法)。

User Option: enable-local-variables ¶

此变量控制是否处理文件局部变量。 可能的值是:

t (the default)

设置安全变量,并查询(一次)任何不安全变量。

:safe

只设置安全变量,不查询。

:all

设置所有变量,不要查询。

nil

不要设置任何变量。

anything else

查询(一次)所有变量。

Variable: inhibit-local-variables-regexps ¶

这是一个正则表达式列表。 如果文件的名称与此列表的元素匹配,则不会扫描它以查找任何形式的文件局部变量。 有关您可能想要使用它的原因的示例,请参阅 Emacs 如何选择主要模式。

Variable: permanently-enabled-local-variables ¶

即使 enable-local-variables 为 nil,默认情况下也会注意某些局部变量设置。 默认情况下,这仅适用于词法绑定局部变量设置,但这可以通过使用这个变量来控制,它是一个符号列表。

Function: hack-local-variables &optional handle-mode ¶

此函数解析、绑定或评估由当前缓冲区的内容指定的任何局部变量。 变量 enable-local-variables 在这里起作用。 但是,此函数不会在 ‘-*-’ 行中查找 ‘mode:’ 局部变量。 set-auto-mode 会这样做,同时考虑到 enable-local-variables(请参阅 Emacs 如何选择主要模式)。

此函数通过遍历存储在 file-local-variables-alist 中的 alist 并依次应用每个局部变量来工作。 它分别在应用变量之前和之后调用 before-hack-local-variables-hook 和 hack-local-variables-hook。 如果 alist 不为零,它只会调用前钩子; 它总是调用另一个钩子。 如果该函数指定了与缓冲区已有的相同的主模式,则此函数将忽略“模式”元素。

如果可选参数句柄模式是 t,那么这个函数所做的就是返回一个指定主模式的符号,如果’-*-’ 行或局部变量列表指定一个,否则返回 nil。 它不设置模式或任何其他文件局部变量。 如果handle-mode 具有除nil 或t 以外的任何值,则’-*-’ 行或局部变量列表中的’mode’ 的任何设置都将被忽略,并应用其他设置。 如果句柄模式为 nil,则设置所有文件局部变量。

Variable: file-local-variables-alist ¶

此缓冲区局部变量保存文件局部变量设置的列表。 alist 的每个元素都采用 (var . value) 形式,其中 var 是局部变量的符号,value 是它的值。 当 Emacs 访问一个文件时,它首先将所有文件局部变量收集到这个 alist 中,然后 hack-local-variables 函数将它们一一应用。

Variable: before-hack-local-variables-hook ¶

Emacs 在应用存储在 file-local-variables-alist 中的文件局部变量之前立即调用此钩子。

Variable: hack-local-variables-hook ¶

Emacs 在完成应用存储在 file-local-variables-alist 中的文件局部变量后立即调用此钩子。

您可以为具有安全局部变量属性的变量指定安全值。 该属性必须是一个参数的函数; 如果函数在给定该值的情况下返回非零值,则任何值都是安全的。 许多常见的文件变量具有安全局部变量属性; 其中包括填充列、填充前缀和缩进制表符模式。 对于安全的布尔值变量,使用 booleanp 作为属性值。

如果要为 C 源代码中定义的变量定义安全局部变量属性,请将这些变量的名称和属性添加到 files.el 的“安全局部变量”部分的列表中。

使用 defcustom 定义用户选项时,您可以通过将参数 :safe 函数添加到 defcustom 来设置其安全本地变量属性(请参阅定义自定义变量)。 但是,使用 :safe 定义的安全谓词只有在加载包含 defcustom 的包后才能知道,这通常为时已晚。 作为替代方案,您可以使用自动加载 cookie(请参阅 Autoload)为选项分配其安全谓词,如下所示:

;;;###autoload (put 'var 'safe-local-variable 'pred)

使用 autoload 指定的安全值定义被复制到包的 autoloads 文件(大多数与 Emacs 捆绑的包为 loaddefs.el),并且在会话开始时 Emacs 就知道这些定义。

User Option: safe-local-variable-values ¶

此变量提供了另一种将某些变量值标记为安全的方法。 它是一个 cons 单元格列表 (var . val),其中 var 是变量名,val 是对该变量安全的值。

当 Emacs 询问用户是否遵守一组文件局部变量规范时,用户可以选择将它们标记为安全的。 这样做会将这些变量/值对添加到安全本地变量值中,并将其保存到用户的自定义文件中。

User Option: ignored-local-variable-values ¶

如果您总是想完全忽略特定局部变量的某些值,则可以使用此变量。 它的值与 safe-local-variable-values 具有相同的形式; 在处理文件指定的局部变量时,将始终忽略列表中出现的值的文件局部变量设置。 与该变量一样,当 Emacs 询问用户是否遵守文件局部变量时,用户可以选择永久忽略它们的特定值,这将更改此变量并将其保存到用户的自定义文件中。 此变量中出现的变量值对优先于安全局部变量值中的相同对。

Function: safe-local-variable-p sym val ¶

如果根据上述标准将值 val 赋予 sym 是安全的,则此函数返回非 nil。

一些变量被认为是有风险的。 如果一个变量有风险,它永远不会自动输入到安全局部变量值中; Emacs 总是在设置有风险的变量之前进行查询,除非用户通过直接自定义 safe-local-variable-values 明确允许一个值。

任何名称具有非零风险局部变量属性的变量都被认为是有风险的。 当您使用 defcustom 定义用户选项时,您可以通过将参数 :risky value 添加到 defcustom 来设置其 risky-local-variable 属性(请参阅定义自定义变量)。 此外,任何名称以’-command’、’-frame-alist’、’-function’、’-functions’、’-hook’、’-hooks’、’-form’、’- forms’、’-map’、’-map-alist’、’-mode-alist’、’-program’ 或 ‘-predicate’ 被自动认为是有风险的。 变量 ‘font-lock-keywords’、’font-lock-keywords’ 后跟一个数字和 ‘font-lock-syntactic-keywords’ 也被认为是有风险的。

Function: risky-local-variable-p sym ¶

如果 sym 是一个风险变量,则此函数返回非零,基于上述标准。

Variable: ignored-local-variables ¶

该变量包含一个变量列表,这些变量不应被文件赋予本地值。 为这些变量之一指定的任何值都将被完全忽略。

‘Eval:’“变量”也是一个潜在的漏洞,所以 Emacs 通常会在处理它之前要求确认。

User Option: enable-local-eval ¶

此变量控制“-*-”行中的“Eval:”或正在访问的文件中的局部变量列表的处理。 t 值表示无条件处理它们; nil 表示忽略它们; 任何其他意味着询问用户对每个文件做什么。 默认值为可能。

User Option: safe-local-eval-forms ¶

此变量包含一个表达式列表,当在文件局部变量列表中的“Eval:”“变量”中找到时,这些表达式可以安全评估。

如果表达式是函数调用并且函数具有 safe-local-eval-function 属性,则属性值确定表达式是否可以安全评估。 属性值可以是调用以测试表达式的谓词、此类谓词的列表(如果任何谓词成功,则它是安全的)或 t(只要参数是常量,总是安全的)。

文本属性也是潜在的漏洞,因为它们的值可能包含要调用的函数。 因此,Emacs 会丢弃为文件局部变量指定的字符串值中的所有文本属性。

12.13 目录局部变量

一个目录可以指定该目录中所有文件共有的局部变量值; Emacs 使用这些为访问该目录中任何文件的缓冲区中的变量创建缓冲区本地绑定。 当目录中的文件属于某个项目并因此共享相同的局部变量时,这很有用。

指定目录局部变量有两种不同的方法:将它们放在一个特殊的文件中,或者为该目录定义一个项目类。

Constant: dir-locals-file ¶

这个常量是 Emacs 期望在其中找到目录局部变量的文件的名称。 该文件的名称是 .dir-locals.el10。 目录中具有该名称的文件会导致 Emacs 将其设置应用于该目录或其任何子目录中的任何文件(可选地,您可以排除子目录;见下文)。 如果某些子目录有自己的 .dir-locals.el 文件,Emacs 会使用它找到的最深文件中的设置,从文件目录开始向上移动目录树。 此常量还用于派生第二个 dir-locals 文件 .dir-locals-2.el 的名称。 如果存在第二个 dir-locals 文件,则除了 .dir-locals.el 之外还会加载该文件。 当 .dir-locals.el 在共享存储库中受版本控制且不能用于个人定制时,这很有用。 该文件将局部变量指定为特殊格式的列表; 有关更多详细信息,请参阅 The GNU Emacs Manual 中的 Per-directory Local Variables。

Function: hack-dir-local-variables ¶

此函数读取 .dir-locals.el 文件并将目录局部变量存储在 file-local-variables-alist 中,该变量对于访问目录中的任何文件的缓冲区来说是本地的,而不应用它们。 它还将目录本地设置存储在 dir-locals-class-alist 中,其中它为找到 .dir-locals.el 文件的目录定义了一个特殊类。 此函数通过调用 dir-locals-set-class-variables 和 dir-locals-set-directory-class 来工作,如下所述。

Function: hack-dir-local-variables-non-file-buffer ¶

此函数查找目录局部变量,并立即将它们应用到当前缓冲区中。 它旨在在非文件缓冲区(例如 Dired 缓冲区)的模式命令中调用,以让它们服从目录局部变量设置。 对于非文件缓冲区,Emacs 在 default-directory 及其父目录中查找目录局部变量。

Function: dir-locals-set-class-variables class variables ¶

该函数为命名类定义了一组变量设置,这是一个符号。 您可以稍后将类分配给一个或多个目录,Emacs 会将这些变量设置应用到这些目录中的所有文件。 变量中的列表可以是以下两种形式之一:(major-mode .alist)或(directory .list)。 对于第一种形式,如果文件的缓冲区打开了从主要模式派生的模式,则应用关联 alist 中的所有变量; alist 应该是 (name . value) 的形式。 主模式的特殊值 nil 表示设置适用于任何模式。 在 alist 中,您可以使用一个特殊的名称:subdirs。 如果关联值为 nil,则 alist 仅适用于相关目录中的文件,而不适用于任何子目录中的文件。

对于第二种形式的变量,如果目录是文件目录的初始子字符串,则按照上述规则递归应用列表; list 应该是此函数在变量中接受的两种形式之一。

Function: dir-locals-set-directory-class directory class &optional mtime ¶

该函数将类分配给目录及其子目录中的所有文件。 此后,为类指定的所有变量设置将应用于目录及其子目录中的任何访问文件。 类必须已经由 dir-locals-set-class-variables 定义。

当 Emacs 从 .dir-locals.el 文件加载目录变量时,它在内部使用这个函数。 在这种情况下,可选参数 mtime 保存文件修改时间(由文件属性返回)。 Emacs 使用这个时间来检查存储的局部变量是否仍然有效。 如果你是直接分配一个类,而不是通过一个文件,这个参数应该是 nil。

Variable: dir-locals-class-alist ¶

此列表保存类符号和相关的变量设置。 它由 dir-locals-set-class-variables 更新。

Variable: dir-locals-directory-cache ¶

这个列表保存了目录名、它们分配的类名和相关目录局部变量文件的修改时间(如果有的话)。 函数 dir-locals-set-directory-class 更新此列表。

Variable: enable-dir-local-variables ¶

如果为零,则忽略目录局部变量。 此变量对于希望忽略本地目录但仍尊重文件本地变量的模式可能很有用(请参阅文件本地变量)。

脚注 (10)

由于 DOS 文件系统的限制,Emacs 的 MS-DOS 版本使用 _dir-locals.el。

12.14 连接局部变量

连接局部变量为具有远程连接的缓冲区中的不同变量设置提供了一种通用机制。 它们根据缓冲区专用的远程连接进行绑定和设置。

Function: connection-local-set-profile-variables profile variables ¶

该函数为连接配置文件定义了一组变量设置,这是一个符号。 您可以稍后将连接配置文件分配给一个或多个远程连接,Emacs 会将这些变量设置应用于这些连接的所有进程缓冲区。 variables 中的列表是一个形式为 (name . value) 的列表。 例子:



     (connection-local-set-profile-variables
	'remote-bash
	'((shell-file-name . "/bin/bash")
	  (shell-command-switch . "-c")
	  (shell-interactive-switch . "-i")
	  (shell-login-switch . "-l")))


     (connection-local-set-profile-variables
	'remote-ksh
	'((shell-file-name . "/bin/ksh")
	  (shell-command-switch . "-c")
	  (shell-interactive-switch . "-i")
	  (shell-login-switch . "-l")))


     (connection-local-set-profile-variables
	'remote-null-device
	'((null-device . "/dev/null")))
Variable: connection-local-profile-alist ¶

此列表包含连接配置文件符号和关联的变量设置。 它由 connection-local-set-profile-variables 更新。

Function: connection-local-set-profiles criteria &rest profiles ¶

此功能将作为符号的配置文件分配给由标准标识的所有远程连接。 标准是一个 plist 标识一个连接和使用这个连接的应用程序。 属性名称可能是 :application、:protocol、:user 和 :machine。 :application 的属性值是一个符号,所有其他属性值都是字符串。 所有属性都是可选的; 如果条件为 nil,则始终适用。 例子:

     (connection-local-set-profiles
	'(:application 'tramp :protocol "ssh" :machine "localhost")
	'remote-bash 'remote-null-device)


     (connection-local-set-profiles
	'(:application 'tramp :protocol "sudo"
	  :user "root" :machine "localhost")
	'remote-ksh 'remote-null-device)

如果条件为 nil,则适用于所有远程连接。 因此,上面的例子相当于

     (connection-local-set-profiles
	'(:application 'tramp :protocol "ssh" :machine "localhost")
	'remote-bash)


     (connection-local-set-profiles
	'(:application 'tramp :protocol "sudo"
	  :user "root" :machine "localhost")
	'remote-ksh)


     (connection-local-set-profiles
	nil 'remote-null-device)

配置文件的任何连接配置文件必须已由 connection-local-set-profile-variables 定义。

Variable: connection-local-criteria-alist ¶

此列表包含连接标准及其分配的配置文件名称。 函数 connection-local-set-profiles 更新此列表。

Function: hack-connection-local-variables criteria ¶

此函数收集与连接局部变量列表中的条件相关联的适用连接局部变量,而不应用它们。 例子:



     (hack-connection-local-variables
	'(:application 'tramp :protocol "ssh" :machine "localhost"))


     connection-local-variables-alist
	   ⇒ ((null-device . "/dev/null")
	      (shell-login-switch . "-l")
	      (shell-interactive-switch . "-i")
	      (shell-command-switch . "-c")
	      (shell-file-name . "/bin/bash"))
Function: hack-connection-local-variables-apply criteria ¶

此函数根据标准查找连接局部变量,并立即将它们应用到当前缓冲区中。

Macro: with-connection-local-variables &rest body ¶

应用默认目录指定的所有连接局部变量。

之后,body 被执行,连接局部变量被解开。 例子:

     (connection-local-set-profile-variables
	'remote-perl
	'((perl-command-name . "/usr/local/bin/perl")
	  (perl-command-switch . "-e %s")))


     (connection-local-set-profiles
	'(:application 'tramp :protocol "ssh" :machine "remotehost")
	'remote-perl)


     (let ((default-directory "/ssh:remotehost:/working/dir/"))
	(with-connection-local-variables
	  do something useful))
Variable: enable-connection-local-variables ¶

如果为零,则忽略连接局部变量。 该变量只能在特殊模式下临时更改。

12.15 变量别名

有时将两个变量设为同义词很有用,这样两个变量总是具有相同的值,并且改变其中一个变量也会改变另一个变量。 每当您更改一个变量的名称时——要么是因为您意识到它的旧名称选择得不好,要么是因为它的含义已经部分改变了——为了兼容性,保留旧名称作为新名称的别名会很有用。 你可以用 defvaralias 做到这一点。

Function: defvaralias new-alias base-variable &optional docstring ¶

此函数将符号 new-alias 定义为符号 base-variable 的变量别名。 这意味着检索 new-alias 的值会返回 base-variable 的值,而改变 new-alias 的值会改变 base-variable 的值。 两个别名变量名称始终共享相同的值和相同的绑定。

如果 docstring 参数不为 nil,它指定新别名的文档; 否则,别名将获得与 base-variable 相同的文档(如果有),除非 base-variable 本身就是一个别名,在这种情况下,new-alias 会在别名链的末尾获取变量的文档。

此函数返回基变量。

变量别名便于用新名称替换变量的旧名称。 make-obsolete-variable 声明旧名称已过时,因此它可能会在将来的某个阶段被删除。

Function: make-obsolete-variable obsolete-name current-name when &optional access-type ¶

此函数使字节编译器警告变量 obsolete-name 已过时。 如果 current-name 是符号,则它是变量的新名称; 然后警告消息说使用当前名称而不是过时名称。 如果 current-name 是一个字符串,这就是消息并且没有替换变量。 when 应该是一个字符串,指示变量第一次被废弃的时间(通常是版本号字符串)。

可选参数访问类型,如果非零,应该指定将触发过时警告的访问类型; 它可以是获取或设置。

您可以使用宏define-obsolete-variable-alias 使两个变量同义并同时声明一个已过时。

Macro: define-obsolete-variable-alias obsolete-name current-name &optional when docstring ¶

此宏将变量 obsolete-name 标记为已过时,并使其成为变量 current-name 的别名。 它等价于以下内容:

(defvaralias obsolete-name current-name docstring)
(make-obsolete-variable obsolete-name current-name when)

这个宏计算它的所有参数,过时名称和当前名称都应该是符号,所以典型用法如下所示:

(define-obsolete-variable-alias 'foo-thing 'bar-thing "27.1")
Function: indirect-variable variable ¶

此函数返回变量别名链末尾的变量。 如果 variable 不是符号,或者 variable 没有定义为别名,则函数返回 variable。

如果符号链中存在循环,则此函数会发出循环变量间接错误信号。

  (defvaralias 'foo 'bar)
  (indirect-variable 'foo)
	   ⇒ bar
  (indirect-variable 'bar)
	   ⇒ bar
  (setq bar 2)
  bar
	   ⇒ 2

  foo
	   ⇒ 2

  (setq foo 0)
  bar
	   ⇒ 0
  foo
	   ⇒ 0

12.16 有限制值的变量

可以为普通 Lisp 变量分配任何有效的 Lisp 对象的值。 但是,某些 Lisp 变量不是在 Lisp 中定义的,而是在 C 中定义的。这些变量中的大多数是在 C 代码中使用 DEFVAR_LISP 定义的。 就像在 Lisp 中定义的变量一样,它们可以取任何值。 但是,有些变量是使用 DEFVAR_INT 或 DEFVAR_BOOL 定义的。 有关 C 实现的简要讨论,请参阅编写 Emacs Primitives,特别是 syms_of_filename 类型的函数的描述。

DEFVAR_BOOL 类型的变量只能取值 nil 或 t。 尝试为它们分配任何其他值会将它们设置为 t:

(let ((display-hourglass 5))
  display-hourglass)
     ⇒ t
Variable: byte-boolean-vars ¶

此变量包含所有 DEFVAR_BOOL 类型变量的列表。

DEFVAR_INT 类型的变量只能采用整数值。 尝试为它们分配任何其他值将导致错误:

(setq undo-limit 1000.0)
error→ Wrong type argument: integerp, 1000.0

12.17 广义变量

广义变量或位置形式是 Lisp 内存中可以使用 setf 宏存储值的众多位置之一(请参阅 setf 宏)。 最简单的位置形式是一个常规的 Lisp 变量。 但是列表的 CAR 和 CDR、数组的元素、符号的属性以及许多其他位置也是存储 Lisp 值的地方。

广义变量类似于 C 语言中的左值,其中 ‘x = a[i]’ 从数组中获取一个元素,而 ‘a[i] = x’ 使用相同的符号存储一个元素。 正如像 a[i] 这样的某些形式在 C 中可以是左值,在 Lisp 中也有一组形式可以是泛化变量。

12.17.1 setf 宏

setf 宏是对广义变量进行操作的最基本方法。 setf 形式类似于 setq,不同之处在于它接受左侧的任意位置形式而不仅仅是符号。 例如,(setf (car a) b) 将 a 的汽车设置为 b,执行与 (setcar ab) 相同的操作,但您不必使用两个单独的函数来设置和访问此类地点。

Macro: setf [place form]… ¶

此宏评估表单并将其存储在适当的位置,它必须是有效的广义变量形式。 如果有多个位置和形式对,则分配按顺序完成,就像 setq 一样。 setf 返回最后一个表单的值。

以下 Lisp 形式是 Emacs 中将用作广义变量的形式,因此可能出现在 setf 的 place 参数中:

一个符号。 换句话说,(setf xy) 完全等价于 (setq xy),并且 setq 本身严格来说是冗余的,因为 setf 存在。 然而,出于风格和历史原因,大多数程序员将继续更喜欢 setq 来设置简单的变量。 宏 (setf xy) 实际上扩展为 (setq xy),因此在编译代码中使用它不会降低性能。

对以下任何标准 Lisp 函数的调用:

aref      cddr      symbol-function
car       elt       symbol-plist
caar      get       symbol-value
cadr      gethash
cdr       nth
cdar      nthcdr

对以下任何 Emacs 特定函数的调用:

alist-get                     process-get
frame-parameter               process-sentinel
terminal-parameter            window-buffer
keymap-parent                 window-display-table
match-data                    window-dedicated-p
overlay-get                   window-hscroll
overlay-start                 window-parameter
overlay-end                   window-point
process-buffer                window-start
process-filter                default-value

如果您传递一个它不知道如何处理的地方表单,setf 会发出错误信号。

请注意,对于 nthcdr,函数的列表参数本身必须是有效的位置形式。 例如, (setf (nthcdr 0 foo) 7) 会将 foo 本身设置为 7。

宏 push(参见修改列表变量)和 pop(参见访问列表元素)可以操作广义变量,而不仅仅是列表。 (pop place) 删除并返回存储在原地的列表的第一个元素。 它类似于 (prog1 (car place) (setf place (cdr place))),只是它只需要对所有子表单进行一次评估。 (push x place) 在原地存储的列表的前面插入 x。 它类似于 (setf place (cons x place)),除了对子表单的评估。 请注意,在 nthcdr 位置上的 push 和 pop 可用于在列表中的任何位置插入或删除。

cl-lib 库为通用变量定义了各种扩展,包括额外的 setf 位置。 请参阅 Common Lisp 扩展中的广义变量。

12.17.2 定义新的 setf 形式

本节介绍如何定义 setf 可以操作的新表单。

Macro: gv-define-simple-setter name setter &optional fix-return ¶

此宏使您可以轻松地为简单的情况定义 setf 方法。 name 是函数、宏或特殊形式的名称。 只要 name 有一个直接对应的 setter 函数来更新它,您就可以使用这个宏,例如 (gv-define-simple-setter car setcar)。

这个宏翻译表单的调用

(setf (name args…) value)

进入

(setter args… value)

这样的 setf 调用被记录为返回值。 这对例如 car 和 setcar 没有问题,因为 setcar 返回它设置的值。 如果您的 setter 函数不返回值,请为 gv-define-simple-setter 的 fix-return 参数使用非零值。 这扩展为等效于

     (let ((temp value))
	(setter args… temp)
	temp)

因此确保它返回正确的结果。

Macro: gv-define-setter name arglist &rest body ¶

这个宏允许比以前的形式更复杂的 setf 扩展。 您可能需要使用这种形式,例如,如果没有要调用的简单 setter 函数,或者如果有一个但它需要与 place 形式不同的参数。

这个宏扩展了形式(setf(name args…) value),首先根据arglist绑定setf参数形式(value args…),然后执行body。 body 应该返回一个执行赋值的 Lisp 表单,最后返回设置的值。 使用这个宏的一个例子是:

(gv-define-setter caar (val x) `(setcar (car ,x) ,val))
Macro: gv-define-expander name handler ¶

为了更好地控制扩展,可以使用 gv-define-expander 宏。 例如,一个可设置的子字符串可以这样实现:

     (gv-define-expander substring
	(lambda (do place from &optional to)
	  (gv-letplace (getter setter) place
	    (macroexp-let2* nil ((start from) (end to))
	      (funcall do `(substring ,getter ,start ,end)
		       (lambda (v)
			 (macroexp-let2 nil v v
			   `(progn
			      ,(funcall setter `(cl--set-substring
						 ,getter ,start ,end ,v))
			      ,v))))))))
Macro: gv-letplace (getter setter) place &rest body ¶

宏 gv-letplace 在定义执行类似于 setf 的宏时很有用; 例如,Common Lisp 的 incf 宏可以这样实现:

     (defmacro incf (place &optional n)
	(gv-letplace (getter setter) place
	  (macroexp-let2 nil v (or n 1)
	    (funcall setter `(+ ,v ,getter)))))

getter 将绑定到返回 place 值的可复制表达式。 setter 将绑定到一个函数,该函数接受一个表达式 v 并返回一个将 place 设置为 v 的新表达式。body 应该返回一个 Emacs Lisp 表达式,通过 getter 和 setter 操作 place。

有关详细信息,请参阅源文件 gv.el。

Common Lisp 注释:Common Lisp 定义了另一种方式来指定函数的 setf 行为,即 setf 函数,其名称是列表(setf 名称)而不是符号。 例如,(defun (setf foo) …) 定义了 setf 应用于 foo 时使用的函数。 Emacs 不支持这个。 在尚未定义适当扩展的表单上使用 setf 是编译时错误。 在 Common Lisp 中,这不是错误,因为函数 (setf func) 可能会在以后定义。

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/advanceflow/elisp.git
git@gitee.com:advanceflow/elisp.git
advanceflow
elisp
Elisp
main

搜索帮助