了解Elisp如何处理变量以及如何在脚本和配置中使用它们。用C和EmacsLisp(Elisp,Lisp编程语言的一种方言)编写,GNUEmacs是一个编辑器,恰好也是Elisp的沙箱。因此,了解Elisp中的一些基本编程概念将很有帮助。如果您是Emacs新手,请先阅读SachaChua的《给 Emacs 新手的资源》精彩博文。本文假设您熟悉常见的Emacs术语,并且能够阅读和评估Elisp代码的简单片段。理想情况下,您还听说过变量作用域的概念以及它在其他编程语言中的工作方式。本文中的示例假设您使用的是相对较新版本的Emacs(v.25之后)。Elisp手册涵盖了Elisp的所有内容,但它是为正在寻找它的人编写的(并且它做得很好)。但是许多人想要能够在更高层次上解释Elisp概念的材料,同时将信息浓缩为最基本的内容。这篇文章也是我尝试响应这个号召,为读者描述基础的大概轮廓。让他们在配置中使用这些技巧,并使他们更容易在手册中查找详细信息。全局变量用defcustom定义的用户设置和用defvar或defconst定义的变量是全局的。使用defcustom或defvar来声明变量的一个非常重要的原因是当一个变量已经绑定时,重新评估它们不会覆盖现有的值。例如,如果您像这样在init文件中绑定my-var:(setqmy-varnil)计算以下表达式将不会用t覆盖变量:(defvarmy-vart)注意这里有一个例外:如果您使用C-M-x快捷方式评估上述声明,它将调用eval-defun函数并用t覆盖变量。通过这种方式,您可以根据需要强制覆盖变量。这种行为是故意的:您可能知道,Emacs中的许多功能都是按需加载的,也称为自动加载。如果这些文件中的声明将变量覆盖为其默认值,它也会覆盖初始化文件中的设置。用户选项用户选项只是用defcustom声明的全局变量。与使用defvar声明的变量不同,这些变量可以使用M-x自定义界面进行配置。据我所知,大多数人并不经常使用它,因为他们认为它很贵。一旦知道如何在初始化文件中设置变量,就没有必要使用它了。许多用户没有意识到的一个细节是,通过自定义设置用户选项可以执行代码,并且可以使用一些时间来运行一些额外的配置指令:(defcustommy-optiont"Myuseroption.":set(lambda(symval)(set-defaultsymval)(message"Set%sto%s"symval)))如果评估此代码并键入M-xcustomize-optionRETmy-optionRET运行自定义接口,lambda匿名函数将被调用,回显区将显示选项的符号名称和值。如果在初始化文件中使用setq改变这个选项的值,匿名函数将不会运行。要在Elisp中正确设置选项,您需要使用函数customize-set-variable。或者,人们在他们的配置文件中使用各种版本的csetq宏来自动处理这个问题(如您所料,您可以通过GitHub的代码搜索发现更复杂的变体)。(defmacrocsetq(symval)`(funcall(or(get',sym'custom-set)'set-default)',sym,val))如果你使用use-package宏,:custom关键字就可以了它为你照顾以上。将上述代码放入初始化文件后,您可以在设置变量时使用csetq宏运行任何现有的设置函数。为了证明这一点,您可以使用此宏来更改上面定义的选项并观察回显区域中的消息输出。(csetqmy-optionnil)动态绑定与词法绑定当您使用其他编程语言时,您可能没有意识到动态绑定和词法绑定之间的区别。当今大多数编程语言都使用词法绑定,学习它们时无需了解变量作用域和变量查找的区别。从这个角度来看,EmacsLisp是特殊的,因为动态绑定是默认选项,而词法绑定需要显式启用。这有一些历史原因,但实际上您应该始终启用词法绑定,因为它更快且更不容易出错。要启用词法绑定,只需将以下行注释为EmacsLisp文件的第一行:;;;-*-词汇绑定:t;-*-或者,您可以调用add-file-local-variable-prop-line,当您选择将变量lexical-binding设置为t后,上面的注释行将被自动插入。当加载包含上述特殊格式行的文件时,Emacs会相应地设置变量,这意味着加载该缓冲区中的代码时启用了词法绑定。要以交互方式执行此操作,您可以调用M-xeval-buffer命令,该命令将词法绑定考虑在内。既然您知道如何启用词法绑定,那么理解这些术语的含义是明智的。对于动态绑定,程序执行期间建立的最后一个绑定用于变量查找。您可以通过将以下代码放入空缓冲区并执行M-xeval缓冲区来对此进行测试:(defuna-exists-only-in-my-body(a)(other-function))(defunother-function()(message"Isee`a',itsvalueis%s"a))(a-exists-only-in-my-bodyt)你可能会惊讶地发现在其他函数查找变量中a起作用了。如果在顶部添加特殊的词法绑定注解后重新运行前面的示例,这段代码将抛出一个variableisvoid错误,因为other-function不识别变量a。如果您使用的是另一种编程语言,这就是您所期望的行为。启用词法绑定时,范围由周围的代码定义。这不仅仅是出于性能原因,时间已经表明词法绑定是首选。特殊变量和动态绑定众所周知,let用于临时建立本地绑定:(let((a"I'ma")(b"I'mb"))(message"Hello,%s.Hello%s"ab))下一个有趣的事情-用defcustom、defvar和defconst定义的变量称为特殊变量,无论是否启用词法绑定,它们都将使用动态绑定:;;;-*-词汇绑定:t;-*-(defunsome-other-function()(message"Isee`c',itsvalueis:%s"c))(defvarct)(let((a"I'mlexicallybound")(c"I'mspecialandthereforedynamicallybound"))(some-other-function)(message"Isee`a',itsvaluesis:%s"a))通过C-heBuffer切换到Messages,查看上面例子输出的消息。使用let或函数参数绑定的局部变量遵循词法绑定变量定义的查找规则,但使用defvar、defconst或defcustom定义的全局变量可以在调用堆栈中的let表达式中进行修改。这种技术允许轻松进行特殊定制,并且经常在Emacs中使用。这并不奇怪,因为EmacsLisp最初提供动态绑定作为唯一选项。这是一个如何临时将数据写入只读缓冲区的常见示例:(let((inhibit-read-onlyt))(insert...))这是另一个常见示例,如何进行区分大小写的搜索:(let((case-fold-searchnil))(some-function-which-uses-search...))动态绑定允许您以作者没有预料到的方式修改函数。对于旨在像Emacs一样使用的程序,这是一个强大的工具和功能。一个警告:您可能不小心使用了在别处声明为特殊变量的局部变量的名称。防止这种冲突的一个技巧是避免在局部变量名中使用下划线。在我当前的Emacs会话中,以下代码仅留下少数潜在的冲突候选者:(let((vars()))(mapatoms(lambda(cand)(when(and(boundpcand)(not(keywordpcand)))(special-variable-pcand)(not(string-match"-"(symbol-namecand))))(pushcandvars))))vars);;=>(tobarraynoninteractivedebuggernil)bufferlocalVariables每个缓冲区都可以有一个变量的本地绑定。这意味着对于任何变量,首先在当前缓冲区中查找局部缓冲区变量以替换默认值。局部变量是Emacs的一个非常重要的特性,例如它们被主模式用来建立缓冲区范围的行为和设置。事实上,您已经在本文中看到了缓冲区局部变量——也就是说,在缓冲区范围内将词法绑定设置为t的特别注释行。在Emacs中,在特殊注释行中定义的缓冲区局部变量也称为文件局部变量。任何全局变量都可以被一个buffer局部变量屏蔽,比如上面定义的变量my-var,你可以这样设置局部变量:(setq-localmy-vart);;或(set(make-local-variable'my-var)t)my-var现在是您正在评估上面代码的缓冲区的本地。如果您对其调用describe-variable,文档将告诉您局部值和全局值。从编程的角度来看,可以分别使用buffer-local-value获取局部值和default-value获取全局值。要删除本地值,您可以调用kill-local-variable。另一个需要注意的重要属性是,一旦变量成为缓冲区的本地变量,随后在该缓冲区中使用setq将只设置本地值。要设置默认值,您需要使用setq-default。因为局部变量意味着缓冲区的自定义,所以它们通常用于模式挂钩中。一个典型的例子是这样的:"gotest-v"(format"gorun%s"(shell-quote-argument(file-name-nondirectorybuffer-file-name))))))这将在M-xcompile中设置go-modebuffer编译命令使用。另一个重要的方面是一些变量自动是缓冲区本地的。这也意味着当你用setq设置这样的变量时,它会为当前缓冲区设置本地绑定。这个特性不应该经常使用,因为隐式行为不好。但如果您愿意,可以使用以下方法创建自动局部变量:(defvar-localmy-automatical-local-vart);;或(make-variable-buffer-local'my-automatical-local-var)variableindent-tabs-mode是Emacs内置的一个例子。如果你在初始化文件中使用setq改变一个变量的值,它根本不会影响默认值。只有局部值在当前当您加载初始化文件时,t缓冲区将被更改。因此,你需要使用setq-default来改变indent-tabs-mode的默认值。结束语Emacs是一个功能强大的编辑器,并且随着您对它的自定义,它会变得更加强大。现在您知道了Elisp如何处理变量以及如何在您自己的脚本和配置中使用它们。
