EMACS-DOCUMENT

=============>集思广益

emacslisp编程tips

我认为EmacsLisp越来越适合用来写应用了,而不仅仅是用来写完美的编辑器而已. 它作为一门编程语言以及编程环境,真的很适合用来写程序. 许多人都同意我的这个观点并开始尝试使用这门语言.

我写EmacsLisp已经15年了,下面是一些诀窍和建议,希望它们对EmacsLisp hacker新手能有所帮助.

1 使用新版Emacs

Emacs最新版已经出到24(注:翻译的时间点,已经出了Emacs25 pretest版). 该版本的Emacs尚未纳入大多数操作系统的仓库中. 但对于Debian和Ubuntu用户来说,你可以使用下面这个仓库/PPA: http://emacs.naquadah.org/.

最新版本的Emacs引入了静态作用域,有内建的包管理机制以及其他很多好东西.

目前我正在维护一个类似的yum仓库. 求打赏.

2 复制粘贴他人代码也并无不可

不用过度追求可复用的模块, 有时候直接复制代码也可以接受.

github上有大量的EmacsLisp 代码可以借鉴和拷贝.

3 已经有了大量的现成代码,直接使用就好

目前已经存在了大量的Emacs库可供使用: 支持 OAuth (and Oauth2, in ELPA)的库, 与 Org Mode 交互的库, 编写 IRC bots 的库, PostgreSQL / MySQL / MongoDB 的接口库. HTTPSMTP 协议库, 甚至Emacs本身就内建了 处理解析 XML 的库. EmacsLisp已经有了几乎所有 格式 的数据定义语言的解释器 .

基本上,作为程序员你所需要的所有东西都已经有了. 即使有些东西暂时还没有现成的包,也很快就会有的.

4 在名称中说明命名空间

EmacsLisp并没有模块系统. 很多人认为这是个很严重的缺陷,但是我觉得这个问题很容易解决.

写EmacsLisp代码时,你需要遵循以下继承关系:

  • 一个package其实就是一个文件或一系列的文件,只不过这些文件是以包的形式来安装的.
    • 最好依照ELPA标准 来组建package
    • 这样你写出来的代码是可移植的,而且是已经声明了依赖关系的.
  • 一个module是package中的一个文件,它provide了一个feature,这样就能被其他文件require了.
    • 一个package中可能有多个module
  • 函数,类,变量,常量,结构体和其他顶层定义都在module中定义.

在遵照这套继承规则的同时,我们还可以遵守一些简单但一致的规则:

  • 以前缀c/C来命名类.
  • 重要的常量名全部大写.
  • module名字中需要标注package名.
  • 若你的package中有多个module,则需要设定一个顶层module作为package的主module,该主module的名称一般与package名称一致. 例如若你的pacakge名为`somepackage',则该package还应该有个名为somepackage.el的文件
  • 在命名函数,变量,方法,类时,需要在名称中说明所属的module名.

    (defun packagename-module-function (args)
      ...)
    
  • 若是在顶层module定义函数:

    (defun packagename-function (args)
      ...)
    
  • 使用 – 来说明定义的是私有函数/变量,就像这样:

    (defun packagename--function (args)
      ...)
    
  • macros的命名方式有些不同,但也尽量在名称中说明所属的package和module, 像这样:

    (defmacro with-package-module-thing (...)
      )
    

5 理解pacakge以及autoload

Autoloads允许惰性加载EmacsLisp库的代码. 下面简要说下它的工作方式:

  • 被标记了 ;;;### autoload 的函数或变量,只有在第一次真正被调用时才加载整个库,再执行函数的真正定义.
  • Emacs从所有的package中加载所有的autoload的速度非常快,因为autoload都很小.
  • 当autoload函数被调用时,会加载整个库.

6 使用CL库(注:现在推荐使用cl-lib替代cl库)

Emacs的核心代码建议不要require cl 库,或使用下面语句:

(eval-when-compile (require 'cl))

若你是为Emacs贡献代码,则你确实有必要遵循这条规范,但若只是为自己写些代码,则放心的用cl 吧,该库提供了许多有用的扩展函数,例如loop, dotimes ,merge 等等.

7 编写测试

Emacs24 之后加入了ERT 库, 用于编写测试很好.

你也可以使用我的fakir 库,该库可以模拟Emacs中的process和file.

最好每个module中的代码都写相应的测试module. 像 Elnode 就有很多的测试案例. Each test module is named after the module that it tests which seems like more good advice.

8 编写函数doc-string

当编写EmacsLisp时, ElDoc 库能提供很大的帮助. 但前提是你程序中所使用的函数/变量都具有doc-string才行. 试着为你的函数/变量编写合适的doc-string吧.

你可以使用我所编写的wikidoc 程序来将函数/变量的doc-string转换为HTML文档.

9 使用EmacsLisp编写命令脚本

Shell脚本写起来很快速也很容易,但当所想实现的功能稍微复杂一点,shell就开始变得难以使用了,这时候你就会想使用一款合适的编程语言来实现这项功能. EmacsLisp就是个不错的选择,下面是一段从我博客中抽取出的例子.

:;exec emacs -batch -Q -l "$0" -f main "$@"
(require 'cl)
(toggle-debug-on-error)
(defun main ()
  (interactive)
  (destructuring-bind (package &optional elpa-parent) command-line-args-left
    ;; Make the elpa dir for this if we need to.
    (when (and elpa-parent
               (not (file-exists-p elpa-parent)))
      (make-directory elpa-parent t))
    ;; Package stuff
    (setq package-user-dir
          (concat
           (or (concat elpa-parent "/")
               user-emacs-directory)
           ".elpa"))
    (setq package-archives
          '(("gnu" . "http://elpa.gnu.org/packages/";)
             ("marmalade" . "http://marmalade-repo.org/packages/";)))
              (package-initialize)
              (package-refresh-contents)
              (if (and (file-exists-p (expand-file-name package))
                       (not (file-directory-p (expand-file-name package))))
                  (package-install-file (expand-file-name package))
                ;; Else must just be a package
                (package-install (intern package)))))

            ;; End

第一行的内容使得Emacs以batch模式来运行脚本,而不是以窗口模式来运行. 注意,第一行(bang line)的内容指明了你调用该脚本的方式,是可以随意修改的:

:;exec emacs -batch -Q -l "$0" -f main "$@"

main 是被调用的函数名称,而 $@ 是传递給shell的参数.

Emacs处理file的能力很强大,它的buffer机制能够很容易的完成那些用shell难以完成的任务.

想查找更多的Emacs Scrilpt请访问EmacsWiki.

10 EmacsLisp也能写面向对象的代码

某些社区强烈反对使用面向对象编程 ,但是在很多时候,面向对象编程其实也是很好用的,where you are trying to declare a category of things that are quite complex for example.

CommonLisp 有CLOS 库,其提供了一套很全面的面向对象编程API, EmacsLisp将之移植了过来,称为EIEIO.

下面是一个例子演示如何在EmacsLisp中定义类和方法:

(defclass some-userc nil
  ((id
    :initarg :id
    :initform (lambda () (some-user--make-uuid))
    :documentation "The id of the person")
   (name
    :type string
    :initarg :name
    :initform ""
    :documentation "Name of the person"))
  "A user record.")

(defun some-user--make-uuid () ; functions can be used by constructors
  "1213243")

(defmethod some-user-greeting ((user some-userc) ; this is a type specifier
                               &optional daytime)
  "Methods are functions and have docstrings."
  (if daytime
      (message "good morning %s" (oref user name))))

(let ((user (some-userc "nic" :name "nic ferrier"))) ; make a user
  (some-user-greeting user t)) ; call the method

11 在写宏时,需要作一些设置

eval-expression-print-level 需要设置为 nil 这样才会显示出Emacs运行的所有的细节.

12 不要写太多的宏

宏的使用场景一般为:

  • 隐藏获取资源的过程
  • 构建领域特定语言
  • 以及抽象控制流

所有这些场景中,只有第一种比较常见, the second is obvious when you need it and the last you should think quite carefully before doing, it's normally for Lisp language hackers, not hackers in Lisp language.

PS:并不是说你就不能写那些操作控制流的宏,而是说在写之前一定要想清楚.

13 闭包很酷,但也更难于调试

如果一个回调函数要用到外部数据,那么我们经常会将之封装成一个闭包:

(let ((x somecapturedthing))
  (lambda (httpcon)
    (real--implementation httpcon x)))

纯函数几乎总是私有函数,不过它也更易于调试. It's also useful sometimes to separate stuff out like this temporarily. It's pretty easy to do quickly, it's probably easy enough that you could write some EmacsLisp to do it automatically.

14 阅读Manual

Emacs自带了很棒的文档 包括了EmacsLisp本身的说明 以及 CL库EIEIO库 的说明.

EmacsWiki中的Elisp Cookbook 也很不错.

15 勤提问题

有问题可以在StackOverflow, reddit 或者 通过 IRC 提问.

16 最后

上面就是所有我认为人们可能不是很清楚的地方了. 若你想使用Lisp编程,我建议使用EmacsLisp, 这是门很实用的语言,强烈推荐.