前面我们学习了基本操作,也走马观花地看了不少emacs lisp的代码。这一章我们做一个lisp的速成讲座。
Lisp的含义是表处理语言。它的代码组成结构都是用括号组成的表来表示的。Lisp中的功能,要么是以函数形式求值,要么本身就是一些特殊表。比如在Lisp语言中,判断分支的if不是语句,也不是函数,而是一种特殊的表。定义函数的方式,也是用一种叫做defun的特殊表。
首先我们搭建一下环境,随便建一个.el为扩展名的文件。然后,我们写一个helloworld的代码吧:
(message (concat "Hello" "," "World" "!"))我们把光标停留在这行上,然后执行C-x C-e,或者执行命令eval-last-sexp,结果就会在下边的状态栏上打印Hello,World.
下面函数的实验,我们就都采用这个方式来进行。
将光标放到要查询的函数上,比如在message中,运行C-h f,就可以查询message函数的帮助文档。
例:
(message "%d" (+ 1 1))例:
(message "%d" (* 123456789 987654321))注意,在emacs lisp中,数字是会溢出的。例错误:
(message "%d" (* 123456789 3145926535897932384626433832795928841971))会报下面的错:
debug(error (overflow-error "3145926535897932384626433832795928841971"))需要注意的是,在emacs lisp中,除法结果是整数:
(message "%d" (/ 128.1 3.0))结果是42
需要注意的是,如果不是整数求余的话,会报错的。
例:
(message "%d" (% 128.1 3.0))会报下面的错:
Debugger entered--Lisp error: (wrong-type-argument integer-or-marker-p 128.1) %(128.1 3.0) (message "%d" (% 128.1 3.0)) eval((message "%d" (% 128.1 3.0)))如果不知道integer-or-marker-p函数的含义,还是老办法,光标移到过去,然后运行C-h f(describe function)去查询它的文档。
例:
(message "%d" (1+ 1))等价于
(message "%d" (+ 1 1))与1+完全类似。例:
(message "%d" (1- 1))结果是22026,请注意结果是整数。
结果是256,2的8次方。
如果给两个参数,那么就log a b,是求以b为底的a的对数。如果省略b,则默认值为e.
(message "%d" (log 256 2)) (message "%d" (log 100))输出为8和4.
输出为:
"The square root of 128 is:11"输出为:
The absolute vaule of -1 is:1sin, cos, tan都是例行公事的函数,反函数是asin, acos, atan,
(message "%d" (sin 0)) (message "%d" (cos 0)) (message "%d" (tan 1)) (message "%d" (asin 1)) (message "%d" (acos 1))例:
(message "%d" (logand 1 0)) (message "%d" (logior 1 0)) (message "%d" (logxor 1 0)) (message "%d" (lognot 0))第一个1与0为0. 第二个1或0为1. 第三个1与0异或为1. 第四个0取非是-1.
在实际编程中,用于与或非逻辑运算的是几个特殊表:
与 and: 对每个参数进行求值,直到遇上一个nil或 or: 对每个参数进行求值,直到遇上一个非nil非 not:not是nil别名。其实取反就是判断一个逻辑值是否为nil.例:
(/= (logand 1 0) (lognot 1))学习了若干判断函数,我们当然需要一个控制结构来使用它,这个控制结构就是if特殊表。if是个特殊表,而不是函数,当然对于我们初学使用来说,这个区别并不重要。if特殊表的结构是(if 条件判断 THEN表 ELSE表 ... )如果判断为非nil,则执行THEN表,否则执行ELSE表。我们看个例子:
(if (atom ()) (message "() is an atom") (message "() is not an atom"))输出当然是:() is an atom
当条件特别多时,if特殊表嵌套可能会导致控制结构比较乱。这时我们可以使用cond特殊表来解决,cond特殊表的结构为:(cond 表1 表2 ...)cond特殊表的会一直执行后面的表,直至遇到任何一个表的值为非nil为止。每个cond中的表都是由两部分组成:判断条件和其他值。cond特殊表执行时,会首先检查判断条件是否nil,如果为nil则继续执行,否则就返回非nil表后面的值。我们还是通过一个例子来学习:
(cond ((= x y) "x and y are the same") ((> x y) "x is greater than y") ((< x y) "x is less than y"))学习了这么多预定义的函数,我们是不是也跃跃欲试,打算写几个自己定义的函数了呢?Emacs Lisp的函数定义要比Common Lisp多几样东西。因为我们前面讲了,emacs lisp的函数就是我们正常调用的命令,所以它要定义交互方式,要定义函数文档。不过好在这两部分都是可选的,完全不知道的情况下,仍然能写出可以正确执行的函数来。
emacs lisp的defun特殊表的格式如下:(defun 函数名 (形参列表) "可选的函数文档" (interactive;可选的交再继续方式)函数体)
我们先写个简版的,实现判断一个数是不是偶数的函数:
(defun evenp (x) (if (= 0 (% x 2)) t nil)) (evenp 2)我们再将其加上描述文档,同时加上一个是不是整数的判断:
(defun evenp (x) "Check if x is an even number or not" (if (and (integerp x) (= 0 (% x 2))) t nil))到目前为止,虽然看起来用括号不太习惯,但是我们已经学会用defun特殊表定义函数,用if和cond特殊表来实现判断。好像也没什么复杂的嘛?
学习了之后再回头看我们之前贴过的代码,是不是一下子就好懂了很多呢?比如这个set-mark-command,不就是几个cond特殊表的组合么:
(defun set-mark-command (arg) ... (cond ((eq transient-mark-mode 'lambda) (kill-local-variable 'transient-mark-mode)) ((eq (car-safe transient-mark-mode) 'only) (deactivate-mark))) (cond ((and (consp arg) (> (prefix-numeric-value arg) 4)) (push-mark-command nil)) ((not (eq this-command 'set-mark-command)) (if arg (pop-to-mark-command) (push-mark-command t))) ((and set-mark-command-repeat-pop (eq last-command 'pop-global-mark) (not arg)) (setq this-command 'pop-global-mark) (pop-global-mark)) ((or (and set-mark-command-repeat-pop (eq last-command 'pop-to-mark-command)) arg) (setq this-command 'pop-to-mark-command) (pop-to-mark-command)) ((eq last-command 'set-mark-command) (if (region-active-p) (progn (deactivate-mark) (message "Mark deactivated")) (activate-mark) (message "Mark activated"))) (t (push-mark-command nil))))遇到具体的函数,我们都可以通过describe-function函数去查看它的文档和代码。
我们先只求会用,有个感性的认识,之后基础回头再补。
几个特殊表:
if特殊表:用来进行判断cond特殊表:多分支判断and特殊表:求值直到遇见nil为止or特殊表:求值直到遇见非nil为止defun特殊表:用来定义函数有了上面的知识,分支、子程序结构都有了,我们可以像写命令式语言一样写代码了。
相关资源:GNU.Emacs.Lisp编程入门_中文版PDF