如无必要,勿增实体。 ----奥卡姆剃刀
2014年初,我曾在某东的基础架构组短暂工作过一段时间,工作内容并不多,但当时的领导用Emacs作为编辑器给我留下深刻印象,那是我第一次见到Emacs。
当时他正在编写一个Nginx的插件,开发环境是c,他给我展示了在Mac下高效(花式)使用Emacs的方式,有语法高亮,代码提示,代码跳转以及眼花缭乱的界面切换等等,我能感受到Emacs带给他极大的满足感和自豪感。
他告诉我,在Unix/Linux终端下,Emacs的快捷键是可以使用的,比如在命令行下Ctrl + a
就可以回到行首,Ctrl + e
就可以回到行尾,Ctrl + Alt + 方向键
可以以单词为单位快速移动光标。这三个操作被我牢牢记下,并在日常的工作中广泛使用。也是这三个操作让我对Emacs产生了巨大的兴趣。
我首先在自己的电脑上安装了黑苹果(后来在新公司的电脑上也安装了黑苹果),然后安装上了Emacs,复杂的配置,丑陋的界面,大量的组合键,在很长一段时间里,Emacs给我带来巨大的迷惘和挫败感,卸载了好多次。但每次想到领导使用Emacs时的场景,我想一定是自己打开方式不对,于是又一次次捡起啦。
一晃几年过去,这期间我参看别的配置,对自己的Emacs环境做了很多修改,总是不满意,Emacs依旧很简陋。因为是依葫芦画瓢,觉得有层窗户纸总也捅不破,Lisp也看的莫名其妙。
有时候很郁闷,自己有大几十万行代码量积累,高并发服务,大数据处理,机器学习算法都搞得还不错,怎么连个编辑器都搞不定。难道这个Emacs骨子里就不行,可是为什么有那么多人趋之若鹜?为什么之前的领导能在Emacs上得到这么大的满足感?
后来我多次尝试Spacemacs的配置,但始终对Spacemacs的哲学摸不透,除了界面漂亮一点,我并没有发现Spacemacs的创举在何处?几次三番之后,偶然间搞清楚了Spacemacs的Leader Key
的使用,顿时感觉发现了新大陆,从此Emacs操作也开始有点飞起的感觉。后来阅读了Spacemacs的文档,研究了下layer的创建和管理方式,觉得Spacemacs正是自己寻找很久的心仪配置,从架构设计
到使用方式
都是。
有了对Spacemacs的认识后,我又开始认真学习《GNU Emacs Lisp编程入门》和《Emacs Lisp 简明教程》这两份教程。学完之后,大有如梦方醒的感觉,原来Lisp如此之简单,Emacs如此之直白,我也长舒一口气,Emacs是名副其实的神的编辑器。
总的来说,之前对Emacs的所有误解,来源于自己对Emacs的认知偏差,以前自己从修改配置的角度只能看到一些边边角角,一旦从操作系统和宏观架构的角度去认识Emacs,就会对Emacs发出由衷的惊叹,Emacs的学习也一下变得简单了。
接下来我想谈谈Emacs的历史,Lisp的设计思想,Emacs的设计架构,Emacs的操作系统属性,Emacs模式等话题,希望从横纵多个角度讨论Emacs。对Emacs有了这些认识之后,相信Emacs对于任何人都会变得简单直白,Emacs能真正提高大家的工作效率和生活质量。
我整理了一份年鉴,提供了计算机领域一些重要产品的发布时间,从时间上感受下Emacs的历史。
可见,在Emacs的早期版本发布时,Linux,Windows,Mac OS这些我们习以为常的操作系统都还没有被开发出来,Emacs的设计思想是非常超前的。那Emacs是不是老古董了呢?是不是倚老卖老了呢?我觉得不是。
Lisp是以列表形式,实现函数式功能的解释性语言。
Lisp(历史上拼写为LISP)是具有悠久历史的计算机编程语言家族,有独特和完全括号的前缀符号表示法。起源于公元1958年,是现今第二悠久而仍广泛使用的高端编程语言。只有FORTRAN编程语言比它更早一年。Lisp编程语族已经演变出许多种方言。现代最著名的通用编程语种是Clojure、Common Lisp和Scheme。
Lisp被设计出来时被用于人工智能处理(早期的基于符号处理的人工智能领域,以图灵测试为目标),关于人工智能发展的几个时期有时间再细聊。
简单讲,以三个个简单特性构成了整个Lisp:
Lisp是指"LISt Processing"(列表处理),和通过把列表放置在括号之间来处理列表(甚至是列表的列表)的编程语言。括号标记了列表的边界。列表是Lisp的基础。
上图定义了1 + 2 + 3 + 4 + 5 = 15
,括号及其中包含的元素构成一个列表,列表的首元素是一个函数,剩余的元素是这个函数的参数,参数可以0个或者多个,参数还可以是函数(比如:(+ 1 2 (+ 3 4 5))
的元素包含了一个列表),因此Lisp的列表定义是递归的。在Emacs的任何Buffer里,通过C-x C-e
可以随时对一个列表求值。
到这里,基本已经搞定Lisp 90%的内容了,我们只要把列表首元素换成其他函数就能完成不同的功能,这就是Lisp,就是这么简单。那究竟有哪些函数可以用呢?C-h f
会列出所有已经定义的函数,选择一个可以查看函数的描述。此外,我们还可以用defun
关键字自定义函数,这个可以自己学一下,这是Lisp剩下的10%。
Lisp可能是大家能学习到的最简练的编程语言了,连他的定义都是递归的,这在现代编程语言中简直不敢想象。
ps: 作为惯例,Lisp版的Hello World
:
(message "Hello, World!")
作为函数式编程语言,函数在Lisp里是第一等公民,可以作为值和参数使用。
Emacs是单进程单线程,内存空间不分区。
就像大多数编程语言一样,Lisp提供了一系列关键字来定义变量(setq, let),常量(defconstant),函数(defun)等,大家平时修改Emacs配置时对这部分最熟悉了,但Emacs在这方面做得更加极致,很多初学者不能从操作系统的宏观角度来看待,因此总觉得与Emacs隔着一层薄薄的窗纱,对Emacs的修改也做不到随心所欲。
我们知道,OS上的任何一个进程都有自己的内存空间,内存用来保存运行时的变量,常量,对象等,而不同的编程语言支持的运行环境通常会将内存划分为不同的区,最典型的是Java虚拟机将内存划分为线程共享内存区(方法区
,堆
),以及线程私有内存区(虚拟机栈
,本地方法栈
,程序计数器
),共5个区,作为单进程多线程的Java程序,通过将内存分区把进程共享变量和线程私有变量分开存放,各自管理,同时提高了垃圾回收的精细控制。
Emacs作为Lisp语言的运行环境,本身就是个虚拟机,虚拟机的核心工作是管理内存。Emacs作为OS上的一个单进程单线程实例,内存空间非常简单 ---- 不分区。Emacs定义的内建变量和函数,与用户定义的函数统统放在一起,而在Lisp里函数与变量是等价的。由此我们可以得到以下结论:
defvar
定义变量,不会修改已经存在的变量值)。C-x C-e
执行Lisp代码,代码运行中定义的变量和函数会被保存到Emacs内存中,等同于为Emacs增加功能。我们可以看到,不同于Java虚拟机等运行环境将系统内建的类,变量,函数单独管理(通过系统类加载器委托加载),Emacs将内建(build-in)变量和函数开放给用户,任何人都可以修改,这也是Emacs高可扩展的由来。不同于其他程序往往在启动时加载配置文件,我们可以任何时候通过C-x C-e
执行Lisp代码,添加或者修改Emacs运行环境的变量和函数,从而实现对Emacs的实时配置。
Emacs中的函数可以分为交互式函数和普通函数,交互式函数是可以通过M-x
执行,普通函数可以通过C-x C-e
求值。要查看当前运行环境中定义(内建和用户定义)的变量和函数,可以通过下面的方法:
C-h f
: 查看函数列表和描述。C-h v
: 查看变量列表和描述。我们定义一个函数,让当前buffer的输入区居中。随便开一个buffer(哪怕是当前buffer也行),通过如下代码定义一个非交互的普通函数buffer-center
,然后执行函数。我们发现,切换到其他buffer,再切换回来时,当前输入区居中显示了。
;; 请使用C-x C-e分别对下面两个表达式求值
(defun buffer-center ()
"Set margins in current buffer."
(setq left-margin-width 24)
(setq right-margin-width 24))
(buffer-center)
你有没有想过org-mode
, markdown-mode
, java-mode
, python-mode
, javascript-mode
, eww-mode
, company-mode
是怎么开发出来的?
你有没有想过给自己开发一个major-mode?
你有没有想过如何实现语法高亮?
你有没有想过如何实现语法补全?
通常,对Emacs的扩展往往集中在几个地方:主题,字体,键绑定,语言支持,交互式函数,Emacs提供了很多内建变量和函数定义,我们可以通过继承来修改已有模式的定义,也可以添加新模式和新功能。
比如:在Emacs要增加新增的语言支持(如:关键词高亮,括号匹配,缩进)是通过正则表达式+Emacs Face
实现的,有时间我们可以单独讲,感兴趣的可以参考:
我们知道,Emacs的编辑对象是Buffer,每个Buffer可以有一个Major Mode和多个Minor Mode,这又是Emacs的一大创举。
Emacs对于Buffer的处理非常简单,一旦一个Buffer被标识为某个模式,Emacs就会为这个Buffer提供该模式定义的服务,比如:语法高亮,智能补全,括号匹配,代码格式化。往往一个模式不会定义所有的功能,因此我们可以给Buffer设定多个Minor Mode,从而让一个Buffer享受多个模式提供的服务。
通常,Emacs根据文件后缀为Buffer设定一个Major Mode,比如:*.csv
对应CSV Mode
,*.sql
对应SQL Mode
,*.org
对应Org Mode
,*.md
对应Markdown Mode
。
通常,Emacs会根据Major Mode自动为Buffer设定几个Minor Mode,我们也可以通过M-x
为Buffer开启或者关闭模式。比如,当我们在*scretch*
(即草稿Buffer)里写SQL时,可以通过M-x sql-mode
为无模式的*scretch*
开启SQL Mode
。
在一个Buffer里提供多种模式的设计,很少出现在其他编辑器里,这个必须要赞一下。
如上图,我们在Emacs里打开一个.md
文件,默认该Buffer的主模式(Major Mode)会被设置为图中红色框的Markdown
模式,然后在绿色框里关联提供了如下几个次模式(Minor Mode):
OrgTbl
: 用来格式化表格。a
: company-mode,用来自动补全。y
: yas-minor-mode,用来提供模板替换功能。p
: smartparens-mode,用来管理成对的标点符号例如括号,方括号,花括号,引号,尖括号, 还有那些让你痛苦的成对的符号。h
: hybrid-mode,Spacemacs提供的Vim和Emacs快捷键混合输入模式,即在Evil的Normal模式下使用Vim快捷键,在Insert模式下使用Emacs快捷键。K
: which-key-mode,它在当前输入的不完整命令( 前缀) 中显示键绑定,用来做快捷键提示。L
: visual-line-mode,提供自动换行功能。另外,我还手动添加了一个M-x window-margin-mode
,让当前Buffer只能输入80列,这样写文档看起来排版比较紧凑,所以大家看截图右边有一片是空白,就是设置了margin的原因,具体代码参见emacs window margin。
Margin
: 设置当前Buffer的输入区域为80列。Emacs的哲学是简单
,Lisp语言如此简单
,它的架构设计如此简单
,它的线程结构如此简单
,它的内存空间布局如此简单
,它的使用方式如此简单
。它的简单
是"奥卡姆剃刀原则"的完美体现。
Emacs是GNU的第一个产品,与之同期的Unix,以及之后的GNU/Linux也遵循了这一设计思想,这是因为在那个时代简单
是软件设计的必然选择。毕竟,在那个时代还没有复杂的面向对象,没有悲剧的《人月神话》,没有冗杂的软件工程。那时候的软件是纯粹的。
我觉得每个人都应该尝试下Emacs,感受它背后的简单纯粹。那些喊着"人生苦短,我用xxx"的朋友,是时候放下酒杯,吃点菜了。
写于王府世纪
王垠 Chez Scheme 的传说
王垠 Lisp 已死,Lisp 万岁!
王垠 一种新的操作系统设计
Wikipedia Microsoft Windows
Wikipedia Mac OS操作系统
Wikipedia Linux
Wikipedia UNIX
Wikipedia Lisp
Wikipedia GNU