学习 Elisp 是如何处理变量的,以及如何在你的脚本与配置中使用它们。

  • 来源:https://linux.cn/article-12150-1.html
  • 作者:Clemens Radermacher
  • 译者:cycoe

GNU Emacs 是由 C 和 Emacs Lisp(Elisp,Lisp 编程语言的一种方言)写成,它是一个编辑器的同时,又碰巧是 Elisp 的沙盒。因此,理解 Elisp 中的一些基本编程概念会对你有一些帮助。

如果你是 Emacs 新手,请先阅读 Sacha Chua 的《 给 Emacs 新手的资源 》精品帖。本篇文章假定你熟悉常见的 Emacs 术语,并且能够阅读并求值 Elisp 代码的简单片段。最好你也听说过变量作用域的概念,知道它在其它编程语言中的作用。本篇文章中的示例假定你使用的是相对较新的 Emacs 版本( v.25 之后的版本 )。

Elisp 手册 包含了 Elisp 的方方面面,但它是写给那些有明确查找目标的人们的(它在这方面也做得相当棒)。但是很多人想要能够在更高的层次上解释 Elisp 概念的材料,同时将信息压缩成最精华的部分。本篇文章也正是我回应这种呼声的一次尝试,为读者描绘基础的大体轮廓。使他们能在配置中用上这些技巧,也让他们在手册中查询细节变得更容易。

全局变量

用 defcustom 定义的用户设置和用 defvar 或 defconst 定义的变量是全局的。使用 defcustom 或 defvar 声明变量的一个非常重要的原因是,当一个变量已经被 绑定(bind),对它们进行重新求值不会覆盖掉已有的值。举个栗子,如果你在初始化文件中对 my-var 进行如下绑定:

(setq my-var nil)

对如下表达式求值不会将变量覆盖为 t:

(defvar my-var t)

注意此处有一个例外:如果你用 C-M-x 快捷键对上述声明求值,它将调用 eval-defun 函数,并将变量覆盖为 t。通过此方式,你可以按需将变量强制覆盖。这种行为是刻意而为之的:你可能知道,Emacs 中的许多特性是按需加载的,也可以称为自动加载。如果那些文件中的声明将变量覆盖为它们的默认值,那它也就覆盖了你初始化文件中的设置。

用户选项

用户选项就是使用 defcustom 声明的全局变量。与使用 defvar 声明的变量不同,这些变量可以用 M-x customize 界面来配置。据我所知,大部分人因为觉得它开销较大而不经常使用。一旦你知道如何在你的初始化文件中设置变量,也就没有理由一定要去使用它了。许多用户没有意识到的一个细节是,通过 customize 的方式设置用户选项能够执行代码,有的时间可用来运行一些附加的配置说明:

(defcustom my-option t  "My user option."  :set (lambda (sym val)         (set-default sym val)         (message "Set %s to %s" sym val)))

若你对这段代码求值,并键入 M-x customize-option RET my-option RET 运行 customize 界面,lambda 匿名函数就会被调用,回显区域就会显示出该选项的符号名与值。

如果你在初始化文件中使用 setq 改变该选项的值,那么匿名函数不会运行。要想在 Elisp 中正确设置一个选项,你需要使用函数 customize-set-variable。或者,人们在他们的配置文件中使用了各种版本的 csetq 宏来自动处理(如你所愿,你可以通过 GitHub 的代码搜索发现更复杂的变体)。

(defmacro csetq (sym val)  `(funcall (or (get ',sym 'custom-set) 'set-default) ',sym ,val))

若你正在使用 use-package 宏,:custom 关键字会替你处理好以上这些。

在你将以上代码放入到你的初始化文件中之后,你便可以使用 csetq 宏在设置变量的同时运行任何现存的 setter 函数。要证明这点,你可以使用此宏来改变上面定义的选项,并观察回显区域的消息输出。

(csetq my-option nil)

动态绑定与词法绑定

当你在使用其它编程语言时,你可能不会意识到动态绑定与词法绑定的区别。当今的大部分编程语言使用词法绑定,并且在学习变量作用域与变量查找时也没有必要去了解它们之间的区别。

如此看来,Emacs Lisp 比较特殊因为动态绑定是默认选项,词法绑定需要显式启用。这里有一些历史遗留原因,但在实际使用中,你应该时刻启用词法绑定,因为它更快并且不容易出错。要启用词法绑定,只需将如下的注释行作为你的 Emacs Lisp 文件的第一行:

;;; -*- lexical-binding: t; -*-

另一种方式,你可以调用 add-file-local-variable-prop-line,在你选择将变量 lexical-binding 置为 t 后,会自动插入如上的注释行。

在加载包含如上特殊格式行的文件时,Emacs 会相应地设置变量,这意味着该缓冲区中的代码加载时启用了词法绑定。若要采用交互式的方式,你可以调用 M-x eval-buffer 命令,它会将词法绑定考虑在内。

既然你已经知道了如何启用词法绑定,那么了解这些术语的含义就很明智了。对于动态绑定,在程序执行期间建立的最后一个绑定将用于变量查找。你可以通过将以下代码放入空缓冲区并执行 M-x eval buffer,以对此进行测试:

(defun a-exists-only-in-my-body (a)  (other-function))(defun other-function ()  (message "I see `a', its value is %s" a))(a-exists-only-in-my-body t)

你可能会很惊讶地发现,在 other-function 中查找变量 a 竟然成功了。

若你在顶部添加了特殊的词法绑定注释后,重新运行前面的示例,这段代码将抛出 variable is void 错误,因为 other-functioin 无法识别变量 a。如果你使用的是其它编程语言,这才是你所期望的行为。

启用词法绑定后,作用域会由周围的代码所定义。这并不单单是性能原因,时间也已经表明了词法绑定才是更受喜爱的。

特殊变量与动态绑定

如你所知,let 用于临时建立局部绑定:

(let ((a "I'm a")      (b "I'm b"))  (message "Hello, %s. Hello %s" a b))

接下来有趣的是——使用 defcustom、defvar 以及 defconst 定义的变量被称为特殊变量,不论词法绑定是否启用,它们都将使用动态绑定:

;;; -*- lexical-binding: t; -*-(defun some-other-function ()  (message "I see `c', its value is: %s" c))(defvar c t)(let ((a "I'm lexically bound")      (c "I'm special and therefore dynamically bound"))  (some-other-function)  (message "I see `a', its values is: %s" a))

通过 C-h e 切换至 Messages 缓冲区,查看上述示例输出的消息。

使用 let 或者函数参数绑定的局部变量会遵循由 lexical-binding 变量定义的查找规则,但使用 defvar、defconst 或 defcustom 定义的全局变量,能够沿着调用栈在 let 表达式中被修改。

这种技巧允许方便地进行特殊定制,并且经常在 Emacs 中被使用。这并不奇怪,毕竟 Emacs Lisp 最开始只提供动态绑定作为唯一选择。下面是一个常见的示例,说明如何向只读缓冲区临时写入数据:

(let ((inhibit-read-only t))  (insert ...))

这是另一个常见的示例,如何进行大小写敏感的搜索:

(let ((case-fold-search nil))  (some-function-which-uses-search ...))

动态绑定允许你采用作者未曾预料的方式对函数进行修改。对于像 Emacs 这样设计使用的程序来说,这是个强大的工具与特性。

有一点需要注意:你可能会意外地使用局部变量名,该变量在其他地方被声明为特殊变量。防止这种冲突的一个技巧是避免在局部变量名中使用下划线。在我当前的 Emacs 会话中,以下代码只留下少数潜在冲突的候选:

(let ((vars ()))  (mapatoms   (lambda (cand)     (when (and (boundp cand)                (not (keywordp cand))                (special-variable-p cand)                (not (string-match "-"                                   (symbol-name cand))))       (push cand vars))))  vars) ;; => (t obarray noninteractive debugger nil)

缓冲区局部变量

每个缓冲区都能够拥有变量的一个局部绑定。这就意味着对于任何变量,都会首先在当前缓冲区中查找缓冲区局部变量取代默认值。局部变量是 Emacs 中一个非常重要的特性,比如它们被主模式用来建立缓冲区范围内的行为与设置。

事实上你已经在本文中见过缓冲区局部变量——也就是将 lexical-binding 在缓冲区范围内设置为 t 的特殊注释行。在 Emacs 中,在特殊注释行中定义的缓冲区局部变量也被称为文件局部变量。

任何的全局变量都可以用缓冲区局部变量来遮掩,比如上面定义的变量 my-var,你可用如下方式设置局部变量:

(setq-local my-var t);; or (set (make-local-variable 'my-var) t)

此时 my-var 对于你在对上述代码进行求值时对应的缓冲区来说就是局部变量。若你对它调用 describe-variable,文档会同时告诉你局部与全局的值。从编程的角度来讲,你可以分别用 buffer-local-value 获取局部值,用 default-value 获取全局值。若要移除局部值,你可以调用 kill-local-variable。

另一个需要注意的重要性质就是,一旦一个变量成为缓冲区局部变量,后续在该缓冲区中使用的 setq 都将只能设置局部的值。要想设置默认值,你需要使用 setq-default。

因为局部变量意味着对缓冲区的定制,它们也就经常被用于模式钩子中。一个典型的例子如下所示:

(add-hook 'go-mode-hook          (defun go-setup+ ()            (setq-local compile-command              (if (string-suffix-p "_test.go" buffer-file-name)                  "go test -v"                (format "go run %s"                        (shell-quote-argument                         (file-name-nondirectory buffer-file-name)))))))

这将设置 go-mode 缓冲区中 M-x compile 使用的编译命令。

另一个重要的方面就是一些变量会自动成为缓冲区局部变量。这也就意味着当你使用 setq 设置这样一个变量时,它会针对当前缓冲区设置局部绑定。这个特性不应该被经常使用,因为这种隐式的行为并不好。不过如果你想的话,你可以使用如下方法创建自动局部变量:

(defvar-local my-automatical-local-var t);; or (make-variable-buffer-local 'my-automatical-local-var)

变量 indent-tabs-mode 就是 Emacs 内建的一个例子。如果你在初始化文件中使用 setq 改变变量的值,根本不会影响默认值。只有在你加载初始化文件时正处在当前的缓冲区的局部值会被改变。因此,你需要使用 setq-default 来改变 indent-tabs-mode 的默认值。

结语

Emacs 是一个强大的编辑器,并且随着你的定制它将变得更加强大。现在,你知道了 Elisp 是如何处理变量的,以及你应如何在你自己的脚本与配置中使用它们。

本篇文章此前采用 CC BY-NC-SA 4.0 许可证发布在 With-Emacs 上,经过修改(带有合并请求)并在作者允许的情况下重新发布。


中lisp文件_关于 Emacs 中的变量你需要知道的事情 | Linux 中国相关推荐

  1. cad中lisp文件给恶作剧_CAD中LISP程序使用方法

    学习园地 | 字号 订阅 1. 对于提供附件下载的,把附件下载就可以了 2. 对于提供的源 LISP 代码, 把代码拷贝. 粘贴到一个文件, 自己起个名或者若 程序里面注释推荐了文件名, 就用推荐的, ...

  2. 8s nfs 挂载文件_把你的树莓派家庭实验室变成一个网络文件系统 | Linux 中国

    导读:使用 NFS 服务器将共享文件系统添加到你的家庭实验室. 本文字数:8554,阅读时长大约:12分钟https://linux.cn/article-12413-1.html作者:Chris C ...

  3. wireshark提取流量包中的文件_返璞归真——流量中提取文件的五种方法

    0×00  简介 本期主要会教大家如何从流量中还原出来文件.下面我将会用5种办法来讲解. 0×01  网络流量提取文件(方法1) 1.  安装依赖 Default yum install -y lib ...

  4. 将dblp中的文件导入到endnote中

    将dblp中的文件导入到endnote中 复制要导入的论文题目到dblp中搜索 点击第一个按钮的第一个链接 跳转后页面点击cite 点击1 Endnote,然后点击2下载. 此时并没有下载文件,页面跳 ...

  5. ubuntu linux root,Ubuntu 中的 root 用户:你应该知道的重要事情 | Linux 中国

    原标题:Ubuntu 中的 root 用户:你应该知道的重要事情 | Linux 中国 当你刚开始使用 Linux 时,你将发现与 Windows 的很多不同.其中一个"不同的东西" ...

  6. cad中lisp文件给恶作剧_最整人的5个CAD问题及解决办法!

    原标题:最整人的5个CAD问题及解决办法! PS:公众号菜单[大师之路]->[自学课堂],可观看大师级CAD在线视频教程. 整蛊一:我的打开保存文件对话框呢? 一般来说,当我们新建文件或者打开文 ...

  7. kass中lisp文件_Lisp之文件操作

    Lisp之文件系统 学习一门语言,除了基本的控制操作外,文件系统API也是很重要的,最近学习了LISP的文件系统API ,和大家分享下心得. open open是很简单的接口,如下所示: (open ...

  8. java调用项目中的文件_详解eclipse项目中.classpath文件的使用

    1 前言 在使用eclipse或者myeclipse进行java项目开发的时候,每个project(工程)下面都会有一个.classpath文件,那么这个文件究竟有什么作用? 2 作用 .classp ...

  9. cmd 将文件夹下文件剪切到另外一个文件_总结java中文件拷贝剪切的5种方式-JAVA IO基础总结第五篇...

    本文是Java IO总结系列篇的第5篇,前篇的访问地址如下: 总结java中创建并写文件的5种方式-JAVA IO基础总结第一篇 总结java从文件中读取数据的6种方法-JAVA IO基础总结第二篇 ...

最新文章

  1. Retrofit 2.0:有史以来最大的改进
  2. 头秃元凶「真面目」首次被揭穿,鹅厂程序员立功了 | Nature子刊
  3. 利用Matlab优化工具箱求解旅行商最短路径问题
  4. 爬取前尘无忧python职位信息并保存到mongo数据库
  5. goto语句_11. Go语言流程控制:goto 无条件跳转
  6. python机器学习、数据分析常用第三方库(实时更新)
  7. php json_encode 中文乱码解决方法
  8. (转)Linux系统中sysctl命令详解 sysctl -p、sysctl -a、sysctl -w
  9. 日期截取年月_2019年7月18日 期权交易日志——用小黄人教你理解期权平价公式...
  10. Redis2-简单动态字符串
  11. 学习如何在matlab用带通滤波器进行滤波
  12. 微信公众平台消息接口开发(8)小黄鸡(小贱鸡)机器人
  13. ic卡信息保存在服务器,智能IC卡网络数据安全保密系统
  14. android fragment实现翻书效果,viewpager实现翻页效果(fragment)
  15. 测试管理之--文档管理
  16. 用C语言VC2010实现数字雨
  17. aes解密流程图_aes流程图
  18. opencv HoughLine 理解
  19. 矩阵分解SVD和NMF
  20. 关于struct的字节数计算(对齐)

热门文章

  1. ubuntu安装php5.3
  2. iOSMVVM(Model-View(View/ViewController) -ViewModel ) 设计模式
  3. ios assetlibrary
  4. jQuery的事件change
  5. JDK和JRE它们之间的关系及区别
  6. 个性化联邦学习PFedMe详细解读(NeurIPS 2020)
  7. ICCV2021多模态视频分析与推理比赛参赛邀请
  8. DBFace升级,模型仅1.3M的轻量级高精度人脸检测模型
  9. oracle数据库导出数据6,Oracle数据库导入导出方法汇总
  10. dubbo 路由配置_Dubbo-go v1.5.1发布,Dubbo 的 Go实现