emacs autoload 集装箱
可能同学们不知道我要表达的是什么,那么先讲两个典型应用吧:
====1====
把你所有的非emacs自带函数集中起来,放到一个文件夹里;然后拆分成许多文件(每个文件里的函数建议不要超过5个),然后函数前面加上 ;;;###autoload ,在你的配置文件里写:
;假设你的这个文件夹路径为 /x/y/z
(lazily "/x/y/z")
通俗的说,效果就是,给这些文件里面的函数生成一个路径表,这些函数文件都不用读取了,只读取这个路径表,用到这些函数的时候再根据这个路径表读取所在文件,对于加快 emacs 的启动速度有一定帮助
====2====
类似于 eval-after-load,利用 autoload 的递归定义,在正式加载前进行配置。
如何递归定义 autoload :
~/a.el 文件中:(autoload 'x "~/b.el" t)
~/b.el 文件中:(autoload 'x "~/a.el" t)
对 a 表达式求值,然后执行 (x) ;会读取 b 文件查找 x 函数的实际定义,但是 b 文件中的定义会让 emacs 到 a 文件中查找……然后就是死循环了(按 "C-g" 中断)。
换一种方式,如果 b 文件中的定义不指向 a 文件,而是指向 x 的实际定义,并且写上一些自定义配置,那么就相当于在执行 x 的时候先加载了 b 文件,然后加载 x 实际定义所在文件,然后执行 x。
这种方法比较适合提高启动速度,只要在配置文件中写 (autoload 'x "b.el" ) ,那么执行 (x) 的时候会加载 b 文件中的配置,再根据 b 文件中的 (autoload 'x "x.el" ) 找到实际的 x 定义并执行 x。
例如, 这个文件 里包含一些 evil-mode 相关的配置,一般情况下都是直接写进配置文件,启动必须加载,或者高级点的用 eval-after-load。前一种情况不用说了,大部分人都这样,这也是 emacs 为人诟病的启动速度的直接原因;eval-after-load 其实已经把需要 eval 的内容读取了,load 后再计算,当然这点内存一般不成问题,一般人也不会写几万行配置文件不是,真正的问题在于 eval-after-load 是和特性(或者说文件)挂钩的,不方便理解,这个文件还得存在于 load-path ,并且要 provid。
这样看起来还不错,但是配置起来却比较麻烦,所以,重点是:
把这种预配置的文件都放在一个文件夹里,假设是 "/x/y/z",在实际的 autoload 定义前写个 ;;;###autoload ,然后在配置文件里写……你懂得
(lazily "/x/y/z")
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
首先,什么是 autoload?
实际上,它就是个函数。
所谓“实际”,是指在实际使用的时候,你是区分不出来 autoload 和普通函数的区别的。
autoload 在 emacs 中也是一种类型,和符号、数字、字符串、函数是同等级别的对象,它和函数的区别类似于函数原型和函数的区别——没有实际的定义,只有简单说明, 调用的时候才加载相关定义。
autoload 对象需要用 autoload 函数生成,类似下面这种:
(autoload 'fn "file" "docstring" interactive type)
参数包含: fn (没有名称你敢怎么调用) file (具体定义实际存储的位置) docstring (使用说明) interactive (如果非 nil 表示可以通过交互方式调用) type (类型:函数、宏、键图)
使用 autoload 有什么好处?简单的说就是节约资源,包括广大人民群众喜闻乐见的缩短 emacs 启动时间……当然作为一个实诚人,我必须说,效果不明显。
更有意义的就是在加载方面,因为加载 autoload 的时候,是把该 autoload 所在文件一起读取,所以理论上,你可以一个 require 也不用写,全用 autoload !某种程度上,这也是一种抽象,你不用再为加载哪个文件犯愁,比如有些该加载的文件没有加载——其实也没什么问题,无非是报错,然后除错——相信自己,这不算什么,尤其是比起不该加载的文件被加载这种情况,起码它还会报错不是?
如果你足够细心,会发现很多 emacs 内置的 elisp 文件,你并不需要显式的 require/load ,用到的时候就加载了,很是神奇。打开这些文件,可以看到里面有一些 “;;;###autoload” 样子奇怪的东东(autoload magic cookies)。
我曾经也认为,在函数定义前面添上 magic cookies 就万事大吉了。其实完全不是这么回事,理论上还需要调用 update-directory-autoloads 之类的函数,根据 magic cookies 生成 autoload 定义文件,然后再读取该文件。
关键的问题是 emacs 内置的 update-directory-autoloads 并不好用,参数很古怪,完全让人无所适从。甚至在 emacs 24 之前,要通过指定外部变量 generated-autoload-file 来确定输出文件……你根本无法理解,函数和函数之间的差距咋就这么大呢!
其实这些也不是没有人能够忍受,如果你是个 M 的话;关键的问题在于,它生成的 autoload 定义,位置参数只是一个文件名!
然后怎么找到这个文件呢?很简单,这个文件要在 load-path 里面,如果你把自己收集整理的函数放在一个文件夹中的话,那么你要把这个文件夹加入 load-path ……当然这也不是什么问题,然后你要给文件起一个名字,比如你把一些函数放在 xxx.el 里面,然后会发生什么事情呢?
假设 emacs 自带了一个 xxx.el(a) 文件,在 load-path 中,它里面定义了一个 autoload 函数 y;而你自己的 xxx.el(b) 文件也加入 load-path,那么当你调用 y 的时候,它会到你自己的 xxx.el(b) 文件中查找,然后就悲剧了。
这有点类似于 lisp-1 中的变量捕获,所以我给它起了一个好听的名字:文件捕获。为了避免这个有着好听名字的问题,我给自己的文件起些不太好听的名字,类似 +xxx.el 这样,人生真是寂寞啊……
所以,如果能够在 autoload 中指定文件位置的话,就可以避免像这样沦落为一个寂寞的二逼青年,为了成为一名正义的二逼青年,我弄了这么个东东,主要特点:
1.不再依赖于 load-path ,你可以自己指定位置,生成的 autoload 定义是这样的(保存在loaddefs文件中的定义)
(autoload 'calc-column (expand-file-name "_autoload/column" *init-dir*) "Not documented.\n\n(fn fn)" t nil)
可以看到,路径的位置是一个表达式,使用相对位置来定位,执行上面定义,得到定义为(读取loaddefs文件后得到的定义)
(autoload "d:/apps/emacs/init.emacs/_autoload/column" "Not documented.
(fn fn)" t nil)
2.loaddefs 文件中使用列表来保存数据,比较文艺;而 emacs 自带的版本,文件名和时间的信息保存在注释中。但是从另一个方面来说,原来版本的 loaddefs 可以直接 load,而现在的这个需要有一个专用的函数来加载,这点比较二逼。
3.使用简单
(lazily 目录)
Read More:
即将发布的Emacs 24.3 的`describe-function'可以知道autoloading了,新函数“autoloadp”还可以测试参数是不是autoloaded object。
补充:如果symbol已经有一个非空的函数定义却不是一个autoload object,
autoload'函数什么也不干,返回nil。如果symbol的function cell为空或者已经是一个autoload object,则autoload'返回一个这样的定义: (autoload filename docstring interactive type)好吧,LinuxTOY不支持Grave accent和Apostrophe的引用形式,被解释成了高亮……
至于缩短Emacs的启动时间,我感觉autoload效果比编译.emacs要好。关于autoload的扩展貌似很少的样子,我只见过Drew的autoload+。
@xfq: 既然有这些,那不是应该有一个 unload 的方法 @xfq: 如果我没有看错的话,现在的 autoload 难道不是这样的? @xfq: autoload+ 是干什么的?不过我觉得如果是在原版的 autoload.el 基础上进行扩展的,应该不会高级到哪里去吧
为什么要有unload方法?不懂。
不好意思我没说清楚,这是对你的文章的补充,不是对我那条新闻的补充
Drew的library大都很复杂,这个应该是他最简单的一个了: http://www.emacswiki.org/emacs-en/download/autoload%2b.el
我只知道unload-feature这个命令,还有个不太常用的cl-unload-function函数。
不明觉厉,楼上诸位好厉害!
unload-feature可以恢复symbol的autoload property,不知你指的是什么?
我的意思是,如果能检测出一个函数是已经加载了的 autoload 的话,那么在用完后可以把它 unload 再还原成 autoload……不过其实也没什么必要
主要是从合理的角度来讲:如果没有这个东西的话,那我们为什么要知道一个函数曾经是不是一个 autoload
http://www.emacswiki.org/emacs-en/download/autoload%2b.el 这个也太简单了吧
虽然简单,但是看到 (require 'autoload) 之后,我连看一眼的兴趣都没有了
引用自unload-feature的docstring:
Standard unloading activities include restoring old autoloads for functions defined by the library, undoing any additions that the library has made to hook variables or to
auto-mode-alist, undoing ELP profiling of functions in that library, unproviding any features provided by the library, and canceling timers held in variables defined by the library.应该可以完成你的要求。
详细说下24.3对autoload的改动:
C-h f(describe-function)能够知道autoloading了。当这个命令被调用时,如果docstring里有key substitution的话,那个函数的feature会被自动load,所以文档将被正确的显示。如果要禁用这个,可以把help-enable-auto-load设置为nil.C-h f现在把之前autoload过的函数显示为"autoloaded"了。setf变成了autoload。autoloadp刚才说过。autoload-do-load用来完成autoload操作。想体验的话,可以下个24.2.93,上述特性都有: ftp://alpha.gnu.org/gnu/emacs/pretest/emacs-24.2.93.tar.xz
真心没看懂。
Emacs从magic comments创建autoload form的机制主要是为了编译Emacs用的,所以不太好用,还是需要手动hack啊。不过el文件重名这种事情还是比较少见的。
以前肯定有人这样尝试过,估计由于文件重名的原因放弃了
这就和使用没有名称空间的语言编程同种感觉
编程其实只是一个技术问题,某种程度上不需要废大脑;给文件、变量、函数……这些东西命名,就是人文问题了,某种程度上,大脑起不到什么作用(当然我指的是靠谱的命名)
就像我在文件名前面缀个“+”的时候,一见到那些文件就想吐……很恶心的感觉
@zer4tul: 把你所有的非emacs自带函数集中起来,放到一个文件夹里;然后拆分成许多文件(每个文件里的函数建议不要超过5个),然后函数前面加上 ;;;###autoload ,在你的配置文件里写:
;假设你的这个文件夹路径为 /home/user/emacs-al/
(loaddefs-update "/home/user/emacs-al" '(getenv "HOME"))
需要注意,第二个路径参数应该在第一个路径参数的上,是它的父目录、爷目录之类
通俗的说,效果就是,给这些文件里面的函数生成一个路径表,这些函数文件都不用读取了,只读取这个路径表,用到这些函数的时候再根据这个路径表读取所在文件
没有命名空间这个确实是Emacs Lisp的痛处。所以Stefan Monnier花了很多精力对cl库进行重写,24.3的cl将改名为cl-lib,里面的许多函数、宏、变量、special form都被改了名字,少数被完全重写了。
不过Emacs的obarray变量在某种意义上也算是一个namespace了。
其实作为配置 emacs 的工具,elisp 已经不错了,只是不那么时髦而已……你不也真指望用 elisp 来干点什么吧
https://github.com/ran9er/init.emacs/blob/master/__loaddefs.el 现在这个版本已经比较满意了
文件读写的问题解决了;文件格式改了下,现在生成的条目要简单了些
(autoload 'xxx "_autoload/xxx" "Not documented.\n\n(fn &rest lst)" nil nil)
由于这些文件都在一个文件夹,所以基准路径是一样的,因此提取了出来
(init-dir (("xxx.el" (20759 39564 208000) (autoload 'xxx "autoload/xxx" "Not documented.\n\n(fn &rest lst)" nil nil)) ("yyy.el" (20759 39564 191000) (autoload 'yyy "autoload/yyy" "Not documented.\n\n(fn)" t nil))))@Kardinal: 其实我是想用Emacs Lisp干点什么的,我的wish list里就有一部分: http://emacswiki.org/emacs/XueFuqiao#toc13
我的观点和Nic Ferrier差不多,比起Common Lisp/Scheme/Clojure这些语言,Emacs Lisp在绝大多数情况下要更加实用,随着Emacs 24对concurrency、closure这些东西的支持越来越好,我相信Emacs Lisp会有很大的发展。
Ps:可能有人不知道Nic,简单介绍下。Nic Ferrier的主要作品是Elnode(一个elisp写的HTTP服务器)、Creole的解析器和HTML导出器(elisp写的)、emacs-maildir(一个基于maildir的MUA,elisp写的),还有一些小的elisp hack。
你太理想主义了,或者说,你的欲望太强了 -___-!!!
甚至我在你的 wish list 里看到了搜索引擎,不会是像 google 这样的吧?难道要分布式计算,让所有的 emacser 提供计算资源?
既然这样,我也狂想一下,用 emacs 实现一个迅雷。主要是因为迅雷的速度实在也是淡定的慢的让人不淡定,用 elisp 实现一个精简版的速度未必要比原生的慢上多少……主要我觉得能狠狠的提升一下 emacs 的支持率
我的想法:上帝的归上帝,emacs 的归 emacs……
何况从来没一种编程语言适合完成所有类型任务,就像没有人能拯救整个世界一样
不过现在最强烈的愿望是实现一个 snippet 系统。yasnippet 挺好,但是就其所拥有的功能而言,我很难理解它为什么那么慢……我都不忍心用它,所以只好忍心不用 snippet
不过话说 closure 支持的怎么样了,不是还要设个文件变量什么的吧?
其实 lexical-let 也还行,不过用 CL 间接实现比较不爽
我看你的配置文件里有个 sudoku,太有爱了……这个怎么样?操作方便不?
PS:你的配置目录像个迷宫,你可以试试我的……不过我不太懂英文,所以一直没有写个说明,以后有时间了,我会考虑写个中文的说明的-__-!!
说到发展,我觉得也就这样了,毕竟强大的乱七八糟,速度又快的 CL 也就那样了……当然不是说它不牛,只能说它太牛,比较小众而已
@Kardinal:
搜索引擎我说的是类似于Nutch这样的,包括全文搜索和robot。
Emacs在下载这方面的库确实很少,我只听说过一个Emacs-wget,而且已经很久没有更新了。
Emacs 24的开发计划里就有一项改进snippets支持,合并skeleton.el、tempo.el、还有expand.el(不知还有没有其他的),并且进行大幅度的重写。
我觉得Emacs Lisp曾经最大的缺点是缺乏lexical binding以及不支持多线程,但是Emacs的开发者(还有Guile的开发者)现在正在努力改善这两个方面,如果这2个问题解决了,下一个大问题我觉得应该是Emacs Lisp代码的运行效率问题。
@Kardinal:
当lexical binding开启时,所有函数自动变为closure。不一定要文件变量,buffer-local也可以。lexical binding出来的时间还不长,一下变成默认不太可能。
@Kardinal:
sudoku的操作还是挺方便的,上下左右填数字就行了。
我的配置文件确实比较混乱,我会抽个时间好好整理一下。
@xfq: 净说实话,如果不能填数字,还能叫 sudoku 么?关键是标记这种辅助的功能。
snippets 那个啥时候能见成效啊?你要是晚告诉我几天,我都要自己弄一个了
emacs 的 lexical binding 还是这么拧巴-__-!! 对象系统早都有了,为什么不先好好弄个闭包系统
添加了个特性,见例2
@Kardinal:
sudoku标记还真没有,也没有多颜色支持。不过有erase、hint……
snippet这个近期内应该是不行了,Emacs的开发者们最近一直在忙的:
不过既然是Emacs 24的计划,在Emacs 25之前应该是能够完成的 -__-
GNU Emacs 24.3 计划在28号发布。