平时我们在看网上的一些配置Emacs的代码时应该注意到了,有多种多样的elisp 变量声明方式,defvar、setq、setq-default、setq-local、let 等等。今天我们深入buffer和window 机制的第二天我们打算来引入一种新的变量类型:buffer local变量,并以此来区分这几种声明变量的方式。
背景知识
全局变量
如果各位读者有其他编程语言的经验,应该对这个概念不会陌生,全局变量是随着程序加载被初始化,在程序的任意位置都可以访问,修改。在程序结束之后它才会消亡。
Emacs 中也有这类变量,在Emacs中声明这类变量可以使用 defvar 和 defcustom。虽然它们二者都是定义全局变量,但是二者设计的初衷完全不同,defvar 是为了定义普通的全局变量,而 defcustom 是为了定义用户可定制选项。
我们先来聊聊 defvar 的一些行为:
defvar用来定义全局变量,不管它是在哪执行的,它所定义的变量都是全局可以访问的。- 它可以在定义变量的时候给一个初始值, 并且可以为变量增加一个字符串的说明文档。
- 如果在定义时发现该变量已经存在,那么它将什么也不做
对于第三条特性,我们可以使用下面的代码来测试
(progn
(defvar custom "Jack")
(message "Hi, %s" custom)
(defvar custom "Tom")
(message "Hi, %s" custom))我们可以看到它打印了两次 "Hi,Jack"。
我们按照上述 defvar 的特征,对 defcustom 逐条审视,发现它在此基础上有一些增强的特性:
- 对于第一条,它完全继承并且行为也一样
- 对于第二条,它的行为有增强,对于描述性的内容,不光可以使用字符串,还可以使用一些关键字进行详细的描述
- 对于第三条,同样的它后续可以通过
setq来设置新值,但是它支持用户通过customize 设置变量的新值,用户后续Customize界面设置的值可以覆盖原始值,并且这个值会被写入~/.emacs.d/custom.el获取通过custom-file变量指定的文件内进行持久化保存
我们可以尝试使用使用一下
(defgroup my-system-config nil
"Configuration group for my personal system settings"
:group 'emacs) ;;挂载到emacs 基础分组下,方便查找
(defcustom my-system-type 0
"Target operating system type for my custom scripts
0 represents Windows, 1 represents Linux, 2 represents MacOS"
:type '(choice (const :tag "Windows(0) 0")
(const :tag "Linux (1) 1")
(const :tag "MacOS (2) 2"))
:group 'my-system-config)我们定义了一个操作系统的设置变量,其中 0代表Windows、1代表 Linux、2 代表MacOS。而变量的默认值是0,后续读者可以尝试执行 M-x customise 来尝试通过界面设置这个变量
定义全局变量实际上还有一个 defconst,它一般用于定义常量,但是在执行时它并不阻止我们后续为变量赋新值。更像是开发者的一个约定,使用它定义了之后,就不要修改它了
变量的修改
变量修改我们可以使用 setq 和 setq-default。
setq 负责给变量赋值,但是如果赋值时变量未定义,它可以定义一个变量,然后再赋值。所以在某种程度上,它也兼职一些 defvar 的工作。但是它定义的变量的声明周期与执行该代码的位置有关。如果是全局位置的,那么它就可以定义全局变量,在函数中那么定义的就是局部变量。
setq-default 同样可以设置变量的值。它与 setq 的核心区别是当一个变量是“缓冲区局部变量”时,它们修改的是不同层级的值。setq 修改的是当前缓冲区的局部值,而 setq-default 修改的是变量的全局默认值
缓冲区局部变量
缓冲区局部变量,英文是 buffer local variable 这是一类特殊的全局变量。我想有一个段子可以更好的描述它。
话说当年军阀混战时,有一个没有文化的军阀看到一群人在打篮球。军阀说“这群人真可怜,一群人只有一个篮球,难怪他们都要追着一个球跑”,然后吩咐手下,给每个人分配一个球。
buffer local 变量与上述段子中军阀给每个人都分配一个篮球类似,Emacs给每个buffer都分配了一个局部的变量。虽然变量名称没变,但是变量在buffer中都是相互独立的。例如我们要设置行号,可以使用 (setq display-line-numbers t) 来显示行号。但是我们切换到其他buffer 中发现其他的buffer无法显示行号。
在当前显示行号的buffer中,查看该变量的值发现它是 t,而其他buffer中的值是 nil。
就像我们前面说到的,setq 只改变当前buffer中的变量,如果要修改变量的全局默认值,可以使用 setq-default。
定义 buffer local 变量,可以使用函数 defvar-local。
defvar-local 与 defvar 类似,也是一处定义,后续再次使用 defvar-local 定义式,新给出的值会被忽略。
(progn
(defvar-local test 0)
(with-current-buffer (get-buffer "*Messages*")
(setq test 1)
(message "test in message buffer is: %d" test))
(message "test in current buffer is: %d" test))设置 buffer local 变量除了使用 setq 和 setq-default 外还可以使用 setq-local。 这三个我暂时称他们为 赋值三兄弟。他们的主要区别在于:
| 场景 | setq | setq-default | setq-local |
|---|---|---|---|
| 变量未定义 | 自动创建一个全局动态变量 | 强行在当前 buffer 中创建该变量的 buffer-local 绑定,并赋值,此时全局变量依然未定义 | 自动创建一个全局变量 效果等同于 setq |
| 变量不是buffer local 类型 | 直接修改该变量的全局默认值 | 强制将其变为当前 buffer 的局部变量然后赋值,全局变量保持不变 | 直接修改该变量的全局默认值,如果已经是buffer local变量,它会修改全局默认值 |
我们将一个变量设置为buffer local 变量可以使用 make-local-variable。它要求这个变量必须是存在的,实际上,我们查看 defvar-local 的源码发现它内部实际调用了 make-variable-local 这个函数。如果我们将上述段子中的篮球比作一个buffer local 变量而运动员比作一个buffer,make-variable-local 要求篮球必须先有一个,然后照着"复制"一个,但是它不负责将篮球交到某个运动员手中。而 defvar-local 则没有这些限制,即使篮球不存在,它也会复制一堆篮球,然后一一交给各个运动员。
练习
我想今天我们主要介绍的是一些基础的概念,所以今天就不像之前那样写一些实用性的工具,读者可以针对这些内容自行修改并创建测试用例来深入理解它们的概念。
- defvar 的“已定义则不重新赋值”特性
;; 第一次定义
(defvar my-test-var "initial")
(message "1: %s" my-test-var) ; => "initial"
;; 第二次定义(新值被忽略)
(defvar my-test-var "new")
(message "2: %s" my-test-var) ; => 仍然 "initial"
;; 但可以用 setq 强制修改
(setq my-test-var "changed")
(message "3: %s" my-test-var) ; => "changed"- setq 在函数内创建局部变量
(defun test-setq-local ()
(setq my-func-var "inside")
(message "Inside function: %s" my-func-var))
(test-setq-local) ; => "inside"
(message "Outside: %s" my-func-var) ;; => Outside: inside- setq-default 与 setq 在 buffer-local 变量上的区别
;; 先定义一个全局变量
(defvar my-global-var "global-default")
;; 创建一个新的 buffer 并在其中设置局部值
(with-current-buffer (get-buffer-create "test-buffer")
(make-local-variable 'my-global-var) ; 显式设为 buffer-local
(setq my-global-var "buffer-local-value")
(message "In test-buffer, setq: %s" my-global-var))
;; 切换回原 buffer,查看全局值
(message "In original buffer: %s" my-global-var) ; => "global-default"
;; 用 setq-default 修改全局默认值
(setq-default my-global-var "new-default")
;; 再次查看 test-buffer 中的值(不受影响)
(with-current-buffer "test-buffer"
(message "In test-buffer after setq-default: %s" my-global-var)) ; => 仍为 "buffer-local-value"- defvar-local 的自动 buffer local 特性
;; 定义 buffer-local 变量
(defvar-local my-buffer-var "default")
;; 在另一个 buffer 中修改局部值
(with-current-buffer (get-buffer-create "buffer-A")
(setq my-buffer-var "value-in-A")
(message "A: %s" my-buffer-var))
;; 当前 buffer 中的值不变
(message "Current: %s" my-buffer-var) ; => "default"
;; 再次用 defvar-local 定义,新值被忽略(同 defvar)
(defvar-local my-buffer-var "ignored")
(message "After redef: %s" my-buffer-var) ; => 仍为 "default"- setq-local 与 make-local-variable 的行为对比
;; 先定义全局变量
(defvar my-var "global")
;; 情况1:用 make-local-variable 显式创建绑定,但尚未赋值
(make-local-variable 'my-var) ; 此时全局值仍为 "global",但当前 buffer 有了自己的局部槽
(message "After make-local: %s" my-var) ; => "global"(因为还未赋值,显示全局值)
;; 此时调用setq 实际在给buffer local 变量赋值
(setq my-var "local")
(message "After setq: %s" my-var) ; => "local"
;; 其他buffer中仍然输出 "global"
(with-current-buffer (get-buffer "*Messages*")
(message "my-var in messages buffer is: %s" my-var))
;; 情况2:直接使用 setq-local(它会自动创建全局变量并设为 buffer-local)
(setq-local my-var2 "new-local")
(message "setq-local created: %s" my-var2) ; => "new-local"
;; 切换到其他 buffer,查看 my-var2 是否被定义(全局变量存在,但不是 buffer-local)
(with-current-buffer (get-buffer-create "buffer-B")
(message "In B, my-var2: %s" my-var2)) ; => 如果全局存在且不是局部,则显示全局值(未定义则为 nil)- setq-default 对未定义变量的行为
;; 变量未定义, 此时它会定义新变量,行为类似 setq
(setq-default my-new-var "default")
;; 此时 my-new-var 是一个全局变量
(message "Global: %s" my-new-var) ; => "default"
;; 判断变量是否为 buffer local 变量
(local-variable-p 'my-new-var) ; => nil 它实际是一个全局变量
;; 切换到另一个 buffer
(with-current-buffer (get-buffer-create "buffer-C")
(message "In C: %s" my-new-var)) ; => 实际创建的是全局变量,所以这里也输出 `In C default`小结
本次我们介绍了Emacs lisp 中的各种变量类型,以及各种变量的定义赋值方式和它们的区别,我们将他们区分为变量定义的三兄弟和变量赋值的三兄弟:
defvardefcustomdefvar-localsetqsetq-defaultsetq-local
以及相对底层的函数 make-variable-local
评论 (0)