一直以来我都在使用Emacs编辑器,使用得越多,越想深入了解Emacs Lisp。在gnu的网站上,我找到了Programming in Emacs Lisp,觉得是很好的学习资料。下面是我的学习笔记贴出来与大家分享。
这个笔记中,对Emacs Lisp中的一些名词:symbols、form、list等没有统一的叫法,对函数、form等也是混合着在使用,主要是为了能让自己更容易了解。
笔记正在增长中,笔记的原文是用emacs muse书写的,需要的朋友可以给我留言。
表处理
Lisp列表
数字,列表中的列表
列表里也可以包含数字:(+ 2 2)。
Lisp里的数据和程序都是相同的方式实现的,他们都是在括号中由单词、数字或者其它列表组成的用空白分隔的列表。因为程序看起来像数据,所以一个程序可以当作数据传递给另一个程序,这是lisp一强非常强大的功能。
Lisp原子
Lisp列表中的单词叫原子(意为原子在Lisp列表中不可再分割成更小的单位)。与原子不同,list可以分隔成更小的单位(car cdr & cons)。
空的列表:(),被称作空列表。与其它的数据类型不同,空列表被同时看作原子和列表。
与自然界的原子一样,Lisp中的原子这个名称来出现得太早(意指与自然界的原子一样,原子还可以再分割)。Lisp中部分原子,比如数组就可以进行分割。但是这种机制与列表的分隔是不同的。如果依据对列表的分隔方式来说,列表中原子就是不可分隔的了。
列表中的空白
额外的空白被用来提高代码的可读性。
'(this list
looks like this)
与
'(this list looks like this)
是相同的。
列表排版
在Emacs Lisp mode下,有多种方法来对Lisp语句进行排版。比如,按<TAB>键将自动缩进当前光标所在行到正确的位置。M-C-/可以格式化当前所选区域中的代码。
运行一个程序
执行Lisp程序时,将执行下列三者之一:
1. 什么都不做,返回列表本身
2. 返回错误信息
3. 把列表中的第一个符号当作命令执行一些操作
放在列表前的单引号被称作引用(quote);当用它来处理列表时,它告诉Lisp不要对列表进行处理。但如果列表前没有单引号,则列表前的第一个元素是特殊的,它被当作命令被执行(Lisp中这些命令被称作函数)。列表(+ 2 2)显示也与加引号的列表的不同,Lisp知道需要用+来处理列表中的其它元素:把后面的数字相加。
生成错误信息
错误信息是由内置的GNU Emacs debugger生成的。进入debugger后,可以用按键q退出debugger。
符号名称和函数定义
Lisp中同一指令可以被绑定到多个名称。
另一方面,在同一时刻一个符号只允许绑定到一个函数定义上。
由于Emacs Lisp的庞大,它有一套按照不同函数功能分类的符号命名规则。如:所有处理Texinfo的函数都心textinfo-开头,而处理邮件的函数以rmail-开头。
Lisp解释器
Lisp的工作方式:首先,它查看列表前是否有单引号,如果有则解释器给出这个列表。如果没有引号,解释器检查列表中的第一个元素是否有对应的函数定义,如果找到则解释器调用函数定义的指令。否则,解释器将打印出错误信息。
复杂一点的内容
Lisp解释器可以对没有单引号且不被括号包围的符号。Lisp解释器将检测符号是否为一个变量。
一些函数不是普通的方法。被用来处理一些特殊的工作,比如定义一个函数。
Lisp求值时,将先对列表内部嵌入的列表进行求值,从内向外。
Lisp解释器工作时从左向右,从一个语句到另一个语句(从上至下)。
编译(Byte Compiling)
Lisp解释器可以解释两种类型的代码:人可以读的代码和另一种被你为byte compiled code的代码。编译过的代码执行更快。
可以用byte-compile-file编译代码。被编译好的字节码文件扩展名为.elc。
求值
当Lisp解释器工作于一个语句上时,这个活动的过程被称为求值(evaluation)。求值完成后解释器将返回函数定义的执行结果,或者在函数出错时给出错误信息。
对内部列表求值
可以把光标停留在内部列表右括号的后面,按C-x C-e执行。
(+ 2 (+ 3 3))
把光标放在括号后面,或者把光标放在代码下面的空行的行首,都可以得到8。如果用C-x C-e对一个数字求值将得到数字自身,这也是数字与符号的不同。
变量
Emacs Lisp中符号可以有一个值绑定到它或者一个函数定义绑定到它。两者不同在于,函数定义是指令的集合。值是可以修改的数字或者其它。符号的值可以是任意的Lisp表达式,比如符号、数字、列表、字符串等。有值的符号通常被称作变量。
符号可以同时有一个函数定义和值。两个是分开的。例:
(defun test_f ()
"test2"
(message "bbb"))
(setq test_f "124")
test_f -> 变量值"124"
(test_f) -> 函数调用显示"bbb"
fill-column一个变量的例子
变量fill-column,每个Emacs缓冲区,这个符号通常被设置成72或70,但也可能有不同的值。可以用C-x C-e对fill-column这个符号求值。
符号可以有值绑定到上面,我们可以绑定变量到值、数字、字符串、列表甚至是函数定义。
函数符号未定义时的错误信息
当我们对fill-column求值时将得到变量的值时并没有在符号外面添加括号。这是因为我们不打算将符号当作函数的名称。
如果fill-column是列表中的第一个元素或者唯一的元素,Lisp解释器将查找绑定到符号上的函数定义。但fill-column不是一个函数定义。当我们对
(fill-column)
求值时将产生错误信息:
---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error: (void-function fill-column)
(fill-column)
eval((fill-column))
eval-last-sexp-1(nil)
eval-last-sexp(nil)
call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------
函数fill-column未定义。
按q退出调试器。
符号没有值时的错误信息
例如对
(+ 2 2)
中的+号求值(光标停留在+的后面,按C-x C-e)时将产生错误信息:
---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error: (void-variable +)
eval(+)
eval-last-sexp-1(nil)
eval-last-sexp(nil)
call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------
这个错误信息与上节函数未定义时的不同。表示变量+未定义。
参数
参数类型
传递给函数的数据类型依赖于函数需要使用何种信息。比如+函数需要数字类型的参数。concat需要字符串类型的参数。substring是一个特殊一点的函数(称作原子粉碎机),它能把从原子类型中解析出一部分数据。
(substring "The quick brown fox jumped." 16 19)
变量值或者列表当作参数
例:
(+ 2 fill-column)
(concat "The " (number-to-string (+ 2 fill-column)) " red foxes.")
参数数量
一些函数可以带多个参数,例如:+、*。
(+) => 0
(*) => 1
(+ 3) => 3
(* 3) => 3
(+ 3 4 5) => 12
(* 3 4 5) => 60
使用错误类型的参数
当传递了错误的参数类型时Lisp解释器将产生错误信息。例如对
(+ 2 'hello)
求值的结果:
---------- Buffer: *Backtrace* ----------
Debugger entered--Lisp error:
(wrong-type-argument number-or-marker-p hello)
+(2 hello)
eval((+ 2 (quote hello)))
eval-last-sexp-1(nil)
eval-last-sexp(nil)
call-interactively(eval-last-sexp)
---------- Buffer: *Backtrace* ----------
错误信息的第一部分直截告诉我们参数类型错误(wrong-type-argument。第二个部分看起来有些迷惑number-or-marker-p,这部分告诉了我们+函数所需要的参数类型。
符号number-or-marker-p说明Lisp解释器检查提供给函数的信息(参数的值)是否是数字或marker(C-@或C-< SPC>设置的位置,mark可以被当作数字进行处理-mark在缓冲区中的字符位置)。Emacs Lisp中+可以将数字和作为数字的marker位置相加。
number-of-marker-p中的p是早期Lisp程序中的用法。p是'predicate'的简写。是早期Lisp研究者所使用的术语, predicate指明了函数用于决定一些属性是true还是false。因此p告诉我们number-or-marker-p是一个根据参数是否为数字或者marker而返回true或者false的函数。另一个以p结尾的Lisp符号包括zerop,这是一个检查参数值是否为0的函数,listp则是一个检测参数是否为一个列表(list)的函数。
最后,错误信息的其它部分将显示出符号hello。这是传递给+的参数值。
message函数
message函数显示信息到回显区。占位符%s表示字符串,%d为整数。例子:
(message "This message appears in the echo area!")
(message "The name of this buffer is: %s." (buffer-name))
(message "The value of fill-column is %d." fill-column)
(message "There are %d %s in the office!"
(- fill-column 14) "pink elephants")
(message "He saw %d %s"
(- fill-column 34)
(concat "red "
(substring
"The quick brown foxes jumped." 16 21)
" leaping."))
设置变量值
有几种方法给变量赋值。set或setq函数,let函数。
使用set
要把符号flowers的值设置为列表'(rose violet daisy buttercup),可以执行下面的语句:
(set 'flowers '(rose violet daisy buttercup))
列表(rose violet daisy buttercup)将显示在回显区。这是set函数的返回值。另一方面符号flowers被绑定到列表;这样符号flower可以看作一个变量,它具有那个列表值。
在对set语句求值后,就可以对符号flowers求值,它将返回set设置的值。当对:
flowers
求值时,回显区将显示(ros violet daisy buttercup)。
这时如果对'flowers求值,将在回显区看到符号自身flowers。
当使用set时,需要在两个参数前加单引号,除非你想对它们进行求值。如果没有加单引号,则解释器将先对参数进行求值,例如对flowers求值,如果flowers之前未赋过值,则将报错,如果对flowers的求值返回了值,则后面的变量值将赋给对flowers求值所返回的值上。这种情况非常少见。
(set 'flowers 'aaa)
(set flowers "123")
(message aaa) ->显示"123"
使用setq
setq与set类似,但setq将自动给第一个参数前加单引号。另一方面,setq允许在一条语句中同时设置多个不同的变量值。例:
(setq carnivores '(lion tiger leopard))
与
(set 'carnivores '(lion tiger leopard))
相同。
setq可以给多个变量赋值,例:
(setq trees '(pine fir oak maple)
herbivores '(gazelle antelope zebra))
尽管我们一直在用赋值('assign'),但有另一种方式思考set和setq;即set和setq使一个符号指向(point)一个列表。
计数器
这是一个在计数器中使用setq的例子。
(setq counter 0) ; 初始化
(setq counter (+ counter 1)) ; 增加
counter
小结
* Lisp程序由表达式组成,表达式可以是列表或者原子。
* 列表由零个或者多个原子或内部列表组成,各元素由空白分隔,被括号包括。列表可以为空。
* 原子是多个字符符号,比如:forward-paragraph,单字符比如+,双引号间的字符串,数字。
* 对自身求值的数字。
* 双引号间的字符串也将对自身求值。
* 当对符号自身求值时,将返回它指向的值。
* 当对列表求值时,Lisp解释器查看列表中的第一个符号所绑定的函数定义。然后按定义的指令执行。
* 单引号,',告诉Lisp解释器应该把后面的表达式按原样返回,不对它进行求值。
* 参数是传递给函数的信息。函数是列表中的第一个元素,其它元素被求值并作为参数传递给函数。