Add New Notes

This commit is contained in:
geekard
2012-08-08 14:26:04 +08:00
commit 5ef7c20052
2374 changed files with 276187 additions and 0 deletions

View File

@@ -0,0 +1,912 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-01T14:42:12+08:00
====== Common LISP Hints ======
Created Wednesday 01 February 2012
http://wiki.ubuntu.org.cn/index.php?title=Common_LISP_Hints&variant=zh-hans
===== 关于Common LISP Hints =====
作者Geoffrey J. Gordon <ggordon@cs.cmu.edu>
修订Bruno Haible <haible@ma2s2.mathematik.uni-karlsruhe.de> 与 Peter Van Eynde <s950045@uia.ua.ac.be>
Friday, February 5, 1993
注:本 Common Lisp 教学文件是针对 CMU 版本的 Lisp ,所以使用者之间可能会因为采用的 Lisp 版本不同,在执行细节上有些微差异。
===== 更多信息 =====
据我所知到最好的 Lisp 教科书是 Guy L. Steele Jr. 所写的 Common LISP: the Language ,该书是在 1984. 由 Digital Press 出版社所出版,它的第一版很容易读,第二版则描述了更多最新的标准。(对于一般的程序设计师而言,第一、二版关于最新标准的些微差异并不会有任何影响。)
另外还有一本由 Dave Touretsky 所写的书也有很多人跟我推荐,不过由于我并没有去读过,所以我也无法评论。
===== 符号 =====
符号(Symbols)就是一串字符。你可以在符号中包含**字母、数字、连接符**等等唯一的限制就是要__以字母开头__。如果你只输入数字最多再以一个连接符开头的话LISP会认为你输入了一个__整数__而不是符号。下面是符号的一些范例
a
b
c1
foo
bar
baaz-quux-garply
你可以像下面的例子一样的使用符号 。(在 > 提示符号后面的就是你的输入给__ Lisp 解释器__的内容而其它的就是 Lisp 解释器所__回显__的结果。而 ";" 分号则是 Lisp 的注释符号,在分号之后到该行结束的数据都会被解释器忽略。)
> (setq a 5) ; 把数值 5 存入 a 这个符号里面。
5
> a ; 取得 a 这个符号所存的值。
5
> __(let ((a 6)) a) __ ; 暂时性地把 a 这个符号的值给设定成 6
6
> a ; 当脱离 let 区块之后, a 的值又变回到 5
5
> (+ a 6) ; 把 a 这个符号的值当作是加法函数的参数
11
> b ; 尝试着取得并没有值的 b 这个符号的值看会发生什么事情?
Error: Attempt to** take the value **of the **unbound symbol B**
有两个比较特别的符号就是__ t 跟 nil__ 。t 这个符号所定义的值就是 t ,而 nil 这个符号所定义的值就是 nil 。 Lisp 分把把 t 跟 nil 这两个值拿来__表示“真”与“假”__。一个最典型会用的 t 跟 nil 的例子就是 if 函数,将会更清楚的解释介绍 if 函数。
> (if t 5 6)
5
> (if nil 5 6)
6
> (if 4 5 6)
5
最后一个例子或许会让你感到很奇怪,不过它并没有错误。原因是 nil 表示“假”而__任何其它的值都表示“真”__。除非你有理由要这样写程序不然通常我们还是习惯用 t 来表示“真”,这样读程序的时候也比较清楚。)
像 t 和 nil 这样的符号被称为__自解析符号__因为他们解析为自身。实际上还有一大类的自解析符号称为__关键字__任一__以冒号开头的符号都是关键字__。下面是一些关键字的应用如下所示
> :this-is-a-keyword
:THIS-IS-A-KEYWORD
> :so-is-this
:SO-IS-THIS
> :me-too
:ME-TOO
===== 数值 =====
整数的定义就是一连串的数字,并且最前面可以选择性的加上+ 或 - 。而实数包含有整数,而且比整数定义广的是,实数还可以有小数点,也可以用**科学记号**表示。有理数则是两个整数相除而得,也就是在两个整数中间加上 / 。 __Lisp 还支持复数类型__利用像是 #c(r i) 这样表示复数,其中 r 表示复数的实部i 表示复数的虚部。上列的任何一种都称做是数值(Number)类型。
下面是一些数值类型的例子:
5
17
-34
+6
3.1415
1.722e-15
#c(1.722e-15 0.75)
对于数值可以进行运算,一些常见的数值函数如 __+, -, *, /, floor,ceiling, mod, sin, cos, tan, sqrt, exp, expt__ 都有内建,而且这些内建的数值函数可以接受任何数值类型的参数。 +, -, *, / 这四个函数的返回值类型会随输入参数的类型而__自动延伸__为**较广**的类型范围,比如说整数加上有理数的返回值,就会是范围较广的有理数;而有理数加上实数的返回值,是实数;实数加上复数的返回值,则是复数。下面是一些例子:
> (+ 3 __3/4__) ;返回值类型范围自动加广
15/4
> (exp 1) ;自然对数的基底 e
2.7182817
> __(exp 3) __ ;e*e*e
20.085537
> (__expt__ 3 4.2) ;指数函数,以 3 为底数,次方数是 4.2
100.90418
> (+ 5 6 7 (* 8 9 10)) ;内建的 +,-,*,/ 四个算数函数都可以接受多个参数值的调用
对于整数绝对值并没有任何参数大小的限制,完全取决于执行时使用的计算机内存够不够。但是要注意,对于大数的计算,越大的数值计算机执行效率一定越慢。(有理数的计算也是会比较慢,尤其是拿来跟不是很大的整数,还有小数的计算速度相比较,更明显。)
===== 点对 =====
点对(cons复数形式conses)就是一个__有两个字段的数据纪录__。由于一些历史上的因素__这两个字段分别称作 "car" 跟 "cdr"__ 。在第一台实现Lisp 语言的机器上, CAR 与 CDR 指令分别表示"Contents of Address Register" 及"Contents of Decrement Register"。而 cons 就是透过这两个缓存器而实作的。) Cons 很容易使用:
> (cons 4 5) ; 设置一个 cons ,其中** car** 设为数字 4 ,而 **cdr** 设为数字 5 。
(4 . 5)
> (cons (cons 4 5) 6) ; 设置一个 cons ,其中 car 设为一个点对(4 . 5),而 cdr 设为数字 6 。
((4 . 5) . 6)
> (car (cons 4 5)) ; 取出 (4 . 5) 的 car 设定值。
4
> (cdr (cons 4 5)) ; 取出 (4 . 5) 的 cdr 设定值。
5
===== 链表 =====
__利用点对(Cons)我们可以创造出很多结构__而当中最简单的或许就是链表(linked list)。
__链表其实就是把 Cons 的 CAR 指定成某些元素,而把 CDR 指定到另一个 Cons 或是 NIL __。如下我们可以经由 list 函数来创造链表。
> (list 4 5 6)
(4 5 6)
看到上面的例子,你应该有注意到 Lisp 在打印链表的时候会有一些原则__它输出的时候会省略掉一些 . 连结点对的点,以及 () 括号__。
而省略的原则如下:
* 如果这个点对的 CDR 是 NIL 的话,那这个 NIL 跟它前面的连结点将不会被印出来;
* 如果这个点对 A 的 CDR 是另外一个点对 B 的话,那在点对 B 前面的连结点以及点对 B 本身的小括号都不会被印出来。
如下例子:
> (cons 4 nil)
(4)
> (cons 4 (cons 5 6))
(4 5 . 6)
>__ (cons 4 (cons 5 (cons 6 nil)))__
(4 5 6)
最后的这个例子其实跟__直接调用函数 (list 4 5 6) __是等价的。
注意** NIL 在这儿的含义就是没有包含任何元素的**__链表__。比如说包含两个元素的链表(a b)中cdr是(b),一个含有单个元素的链表;包含一个元素的链表(b)cdr是nil故此这里必然是一个没有元素的链表。
**NIL 的 CAR 跟 CDR 都定义成 NIL 。**
如果我们把链表指给任何变量那就可以如下__当成堆栈(stack)__来使用
> (setq a nil)
NIL
> (push 4 a)
(4)
> (push 5 a)
(5 4)
> (pop a)
5
> a
(4)
> (pop a)
4
> (pop a)
NIL
> a
NIL
===== 函数 =====
之前我们看过函数(Functions)的例子了。下面是其它函数的例子:
> (+ 3 4 5 6) ; 加法函数可以接受任意多的输入参数
18
> (+ (+ 3 4) (+ (+ 4 5) 6)) ; 或者你也可以像这样加,哈~
22
> (__defun__ foo (x y) (+ x y 5)) ; 定义一个叫做 foo 的函数
FOO
> (foo 5 0) ; **调用函数**,传入的参数分别是 5 跟 0
10
> (defun fact (x) ; 以递归调用的方式定义函数 fact
(if (> x 0)
(* x (fact (- x 1)))
1))
FACT
> (fact 5)
120
> (defun a (x) (if (= x 0)__ t __(b (- x)))) ; 以两个函数相互调用的递归方式来定义函数
A
> (defun b (x) (if (> x 0) (a (- x 1)) (a (+ x 1))))
B
> (a 5)
T
> (defun bar (x) ; 一个函数的定义里面如果有很多语句句的话
(setq x (* x 3)) ; 那整个函数的返回值,
(setq x (/ x 2)) ; 将会是__最后的一个语句__
(+ x 4))
BAR
> (bar 6)
13
当初我们在定义 foo 函数的时候,要求要两个传入值 x 及 y 。所以每当要调用 foo 时都要__恰好__给它两个传入值第一个传入值将会变成在 foo 函数里面的 x 变量值,而第二个传入值将会变成在 foo 函数里面的 y 变量值。而在 Lisp 里面,大多数变量其实都是 __lexically scoped __也就是说如果 foo 的定义里面有调用 bar 函数,在 bar 函数被调用的时候,依然看不到 foo里面 x 变量的值滴。这种指定变量的值所存在的可视范围称做是__绑定(binding)__。
在函数定义的时候其实有些传入值可以当作是__选用的/非必需__的。任何传入值只要在前方加上__ &optional __就会变成是选用的/非必需的。
如下例子:
> (defun bar (x __&optional__ y) (if y x 0))
BAR
> (defun baaz (&optional (x 3) (z 10)) (+ x z))
BAAZ
> (bar 5)
0
> (bar 5 __t__)
5
> (baaz 5)
15
> (baaz 5 6)
11
> (baaz)
13
你可以使用一或二个参数调用 bar 函数。如果你只有用一个参数调用 bar 函数,那个参数就会设定给 x 而__ y 的默认值则会是 NIL__ 如果你使用两个参数调用bar 函数,那 x 跟 y 就分别被设定成第一及第二个传入参数。
而 baaz 函数__有两个选用参数并且这两个参数都有默认值__如果 baaz 时,只有给它一个传入参数,则 z 的直就会是默认值 10 ,而非 NIL 而如果调用baaz 函数时,没有给任何传入值,则 x 跟 z 的值就会是默认值 3 跟 10 。你可以让你设计的函数接受任意多个的输入参数只要在参数列加上一个__ &rest__的参数就可以了 Lisp 将会把所有没有被指到到变量名称的参数搜集再起变成__一个链表__变且把这个链表指定给** &rest 的参数**。如下:
> (defun foo (x __&rest__ y)** y**)
FOO
> (foo 3)
NIL
> (foo 4 5 6)
(5 6)
最后,你还可以将你的函数设计另一种输入**选用参数**的方式就是透过__关键字(keyword)参数__。这种方式的传入参数没有前后次序性因为输入参数的时候都要指定关键字参数的名称。
> (defun foo (__&key__ x y) (cons x y))
FOO
> (foo :x 5 :y 3)
(5 . 3)
> (foo :y 3 :x 5)
(5 . 3)
> (foo :y 3)
(NIL . 3)
> (foo)
(NIL)
就算是利用 &key 设定的 keyword 参数,也可以有默认值,如下范例:
> (defun foo __(&key (x 5))__ x)
FOO
> (foo :x 7)
7
> (foo)
5
===== 打印Printing =====
有些函数会导致输出,最简单的一个是 print ,它会把参数打印出来并**返回参数的值**。
> (print 3)
3
3
第一个 3 是因为调用 print 函数而__把参数输出到屏幕上__第二个 3 则是调用函数之后的__返回值__。
如果你希望输出结果复杂一点,你可以使用 format 函数。见下面范例:
> (format t "An atom: ~S~%and a list: ~S~%and an integer: ~D~%"
nil** (list 5) **6)
An atom: NIL
and a list: (5)
and an integer: 6
在调用 format 函数的时候第一个参数可以__ t nil 或是一个流__。其中 t 表示要输出到__终端上__。 nil 表示**不要输出**而是把原本要打印出来的字符串__当作是函数的返回值__传回。流是用于输出的通用方式它可以是一个指定的文件 或者一个终端,或者另一个程序。这里不再详细描述流的更多细节。
第二个输入的参数则是一个__格式化的模版__也就是一个字符串字符串里面可能含有一些的**格式化指令**。
其它剩下的参数则是跟之前字符串里面的格式化指令是相对应的Lisp 将会把剩下的参数用来代换至字符串里面相对应的格式化指令。 Lisp 会根据格式化指令的__适当属性__把其余参数用适当的方式带换掉之后在输出格式化之后的字符串。
format 函数的__返回值预设会是 nil __除非在调用 format 函数的第一个参数是 nil ,如此,则不会把格式化之后的字符串输出到任何对象,而是会把格式化之后的字符串当作是函数调用的返回值。
在上面范例里面用到的三个不同的格式化指令__~S, ~D 跟 ~% __。第一个 ~S 会接受**任何的Lisp 对象**,并且会用**该对象的可以显示的方式**来取代掉 ~S (当中可以显示的方式跟直接利用 print 函数输出该对象是一样的方式)。第二个 ~D 会接受**任何的整数值**。第三个 ~% 则不会被任何之后的输入参数所取代可是它会__自动转换成换行__的指令。
另外还有一个有用的格式化指令是__ ~~ __它会自动输出成只有一个 ~ 。如果还要更多、更多额外的格式化指令,可以参考其它的 Lisp 手册。
===== 表格与顶层循环 =====
你输入到 Lisp 解释器的那些东西被称为__表格forms__Lisp 解释器反复读取每个表格运算它并且把结果打印出来。这个一再重复的过程就称作__“读取—求值—打印”循环__。
有些表格可能会引发错误(也就是程序代码没写好啦)。当发生错误后, Lisp 会进入__调试模式__以便让我们找出错误发生的原因。每个 Lisp 版本的调试模式都不太一样,但在大多数的调试模式下输入 "help" 或 ":help" 后,就应该会显示相关的帮助信息。
一般而言表格里的数据要么就是无法再细分的__原子atom__像是**符号、整数、字符串**⋯⋯,这些都是属于无法再细分的原子,**不然表格里的数据就是一个链表**。如果表格的数据是原子,那 Lisp 通常很快就可以运算出它的返回值符号返回值就是它所表示的值整数和字符串的返回值就是它们本身而已。但如果表格的数据是一个__链表__那 Lisp 会把这个链表的**第一个元素**当作是**函数的名称**,把**其它元素运算完之后的值**当作是输入参数,然后把这整个链表当作是**函数调用**,举例来说,如果表格的数据是 (+ 3 4) Lisp 会把 + 当作是**最后要调用**的函数名称然后它逐步求值3 运算之后返回值是 3 4 运算之后返回值是 4 ,而后调用 + 这个函数,而传入 + 这个函数的参数则是刚刚已经运算完的值 3 跟 4 ,因此调用完 + 这个函数的返回值会是 7 ,最后 Lisp 再把它打印给我们看。
译注:而我们在使用 Lisp 解释器的时候,位在 > 之后要给它的,就是位在顶层的“读取—求值—打印”的循环。
位于顶层的“读取—求值—打印”循环其实有其它的好处其中一个好处就是可以随时取出__之前__运算过的表格数据Lisp 用 *, **, 跟 *** 分别表示在此之前的三个返回值。如下例:
> 3 ; 要求值的表格是 3 ,所以返回值是 3
3
> 4 ; 要求值的表格是 4 ,所以返回值是 4
4
> 5 ; 要求值的表格是 5 ,所以返回值是 5
5
> *** ; 要求值的表格是,在这之前推三步的那个表格,所以求值 3 之后,返回值是 3
3
> *** ; 要求值的表格是,在这之前推三步的那个表格,所以求值 4 之后,返回值是 4
4
> *** ; 要求值的表格是,在这之前推三步的那个表格,所以求值 5 之后,返回值是 5
5
> ** ; 要求值的表格是,在这之前推两步的那个表格,所以求值 4 之后,返回值是 4
4
> * ; 要求值的表格是,在这之前推一步的那个表格,所以求值 4 之后,返回值是 4
4
===== 特殊表格 =====
有一些比较特殊的表格Special forms看起来就像是函数调用可实际上却不是。这些特殊表格包含有__流程控制命令__如 if 和 do loop 语句等以及用来__设定变量的命令__如 setq, setf, push 与 pop 还有__用来定义的命令__如定义函数的 defun 及定义结构的 defstruct 还有__用来绑定的命令__如 let 。(当然上面并没有提及所有的特殊表格,往下看,还会继续介绍其它的特殊表格)
一个很有用的特殊表格 quote 是用来避免对它的输入参数进入求值也就是它会__让输入参数以原来的形式返回__并不会先经过运算的步骤。举例如下
> (setq __a __3) ; set quote.
3
> a
3
> (quote a)
A
> 'a ; **'a 是 (quote a) 的缩写**
A
另一个类似的特殊表格就是 __function 表格__function 会让它的输入参数__被视作是某个函数而不是被拿来求值__。举例如下
> (setq + 3)
3
> +
3
**> '+**
**+**
> (function +)
#<Function + @ #x-fbef9de>
> #'+ **;#'+ 是 (function +) 的缩写**
#<Function + @ #x-fbef9de>
function 这个特殊表格常常被用在要**把函数当作是参数来传递的时候**。本文后面会继续介绍到一些例子,就是把函数拿来当作是输入参数,此时就会需要用到 function 这个特殊表格。
===== 绑定 =====
绑定(Binding)是 lexically scoped 的变量值设定。它发生在当函数调用时候,参数列的变量是用**绑定的方式**设定变量值在函数调用期间此时此函数定义时的参数列其值被绑定在函数调用发生时的输入参数。其实不管在哪程序里面的哪里你也可以利用__ let __这个特殊窗体来绑定变量值其使用形式如下
(let ((var1 val1)
(var2 val2)
...)
body)
Let 把 var1 绑定成 val1 ,把 var2 绑定成 val2 ,如此类推,然后它会执行 body 这一区块的程序语句。 上面 Let 特殊窗体里面的 body 程序区块语句执行的结果就会像是在函数调用时的程序语句有一样的效果。如下范例:(译注这像是函数调用只是__把参数列改成 let 特殊窗体__而已而 body 执行完之后的返回值,就会是函数返回值。)
> (let ((a 3)) (+ a 1)) ; 在 let 窗体里面,绑定 a 为 3然后执行 a+1 ,返回值就是 4 。
4
> (let ((a 2)
(b 3)
(c 0))
(setq c (+ a b))
c)
5
> (setq c 4)
4
> (let ((c 5)) c)
5
> c
4
__如果有绑定值是 NIL 的,如 (let ((a nil) (b nil)) ...) ,就可以缩写成 (let (a b) ...)__ 。 Let 特殊窗体里面的绑定值 val1, val2 ... 等的值不能参照 var1, var2 ... 等,因为绑定正在发生,还没有结束。如下范例:
> (let ((x 1)
(y (+ x 1)))
y)
Error: Attempt to take the value of the unbound symbol X
如果变量 x 在上面这段程序执行之前已经有全域变量值,那就会发生很莫名奇妙的结果,如下范例:
> (setq x 7)
7
> (let ((x 1)
(y (+ x 1)))
y)
8
还有一个 let* 也是特殊窗体,它跟 let 很像但是不同的地方是__ let* 可以允许绑定值参考之前已经绑定的变量__。如下范例
> (setq x 7)
7
> (let* ((x 1)
(y (+ x 1)))
y)
2
下面这样的窗体
(let* ((x a)
(y b))
...)
其实就__等同于__如下
(let ((x a))
(let ((y b))
...))
===== 动态作用域 =====
let 跟 let* 这样的特殊窗体提供了 lexical scoping ,那就像你在写 C 或是 Pascal 程序**所预期一样**的变量可视范围。而还有一种动态作用域(Dynamic scoping)就如同 BASIC 语言所提供的一样,**如果你指定一个变量值给动态作用域的变量,那不管你在何时去读取变量的值,都会是**__一开始指定__**的那个变量值,除非你有给它另一个新变量值**,以取代之。
在 Lisp 里面这些动态作用域的变量被称做是__特殊变量__(special variables),你可以透过 defvar 特殊窗体来定义特殊变量。下面是一些 lexically 跟 dynamically scoped 变量的例子。
在下面的这么范例里面, check-regular 函数里面调用了 regular 这个__一般变量__(亦即lexically scoped 的变量)。因为 check-regular 函数的定义是在 let 区块之外,所以 **let区块里面的 regular 绑定并不会影响到 check- regular 函数里面 regular 变量值**所以check-regular 的返回值是 regular 变量的全域可视范围的值。
> (setq regular 5)
5
> (defun check-regular () regular)
CHECK-REGULAR
> (check-regular)
5
> (let ((regular 6)) (check-regular))
__5__
在下面的这么范例里面, check-special 函数里面调用了 special 这个**特殊变量**(亦即dynamically scoped 的变量)。因为在 let 区块里面有一段暂时调用了 check-special 这个函数,而且 let 有暂时绑定 special 特殊变量新值,所以 check-special 会返回的是受到let 区块绑定影响的区域变量值。
> (__defvar__ ***special*** 5)
*SPECIAL*
> (defun check-special () *special*)
CHECK-SPECIAL
> (check-special)
5
> (let ((*special* 6)) (check-special))
6
为了方便记亿与区别通常会__把特殊变量的名称前后会用 * 包围起来__。__特殊变量主要被用在当作是全域变量__因为程序设计师通常会预期**区域变量是 lexical scoping ,而全域变量是 dynamic scoping **。
如果需要更多关于 lexical scoping 跟 dynamic scoping 的区别请参看_Common LISP: the Language_ 这本书。
===== 数组 =====
函数 make-array 可以产生数组(Arrays),而函数 aref 则可以存取数组里面的元素。数组里所有元素的**初始则设定值是 NIL **。如下范例:
> (make-array '(3 3))
#2a((NIL NIL NIL) (NIL NIL NIL) (NIL NIL NIL))
> (aref __ * __1 1)
NIL
> (make-array 4) ; 一维数组的维度不需要额外的小括号
#(NIL NIL NIL NIL)
数组的索引值必定是由__ 0 开始__起算。
继续往下看,将会学到如何设定数组的元素。
===== 字符串 =====
所谓的字符串(Strings)就是被__两个 " __所包夹在中间的字符序列。Lisp 实际上是__把字符串视为是可变长度的字符数组__。如果要表示的字符串里面本身就包含有 " 的话,那需要在 "前面加上倒斜线__ \__ ,而用连续的两个倒斜线来是表示字符串里面的一个倒斜线。如下范例:
"abcd" 包含有 4 个字符
"\"" 包含有 1 个字符
"\\" 包含有 1 个字符
下面是一些用来处理字符串的函数范例:
> (**concatenate **'string "abcd" "efg") ; 连接字符串用 concatenate 函数
"abcdefg"
> (__char __"abc" 1)
#\b ; Lisp 会在字符前面加上 **#\ **用来表示字符。
> (aref "abc" 1)
#\b ; 请记住,字符串其实就是字符数组而已。
连接字符串用的 concatenate 函数实际上可以用来连接__任何类型的序列__
> (concatenate** 'string** '(#\a #\b) '(#\c))
"abc"
> (concatenate **'list** "abc" "de")
(#\a #\b #\c #\d #\e)
> (concatenate **'vector **'#(3 3 3) '#(3 3 3))
#(3 3 3 3 3 3)
===== 结构 =====
Lisp 的结构(Structures)就类似 C 语言的 struct 跟 Pascal 语言的 record 。下面是一个范例:
> (__defstruct__ foo
bar
baaz
quux)
FOO
这个范例定义了一个名为 foo 的__数据类型__这个类型的结构实际上包含了**三个字段**。在定义结构的同时实际上它也定义了四个可以操作这个数据类型的的函数分别是make-foo, foo-bar, foo-baaz, 跟 foo-quux 。第一个函数 make-foo 可以用来产生 foo 数据类型的对象,而其它三个函数则可以用来取得 foo 数据类型当中对应的数据域位。下面是,如何使用这些函数的范例:
> (make-foo)
#s(FOO :BAR NIL :BAAZ NIL :QUUX NIL)
> (make-foo :baaz 3)
#s(FOO :BAR NIL :BAAZ 3 :QUUX NIL)
> (foo-bar *)
NIL
> (foo-baaz **)
3
只要是 foo 结构所有的字段,在产生对象时候用的 make-foo 函数都可以接受对应字段的keyword 参数。而存取数据域位的取用函数则可以接受一个 foo 对象当作是输入参数,并且返回该结构里对应的数据域位之值。
继续往下看,将会学到如何设定结构里各字段的值。
===== Setf =====
在 Lisp 里面有__某些窗体实际上表示的就是内存里的位置__举例来说如果 x 是foo 数据类型的结构的话,那 (foo-bar x) 表示的就是 x 里面的 bar 数据域位。另外,如果 y 是一维数组,那 (aref y 2) 表示的就是 y 数组里面的第三个元素。
而 setf 特殊窗体可以接受两个参数,第一个参数是一个内存里的位置,而第二个参数在被评估求值之后,**所评估出来的值将会被存入第一个参数所指的内存位置**。举例如下:
> (setq a (make-array 3))
#(NIL NIL NIL)
> (aref a 1)
NIL
> (setf** (aref a 1)** 3)
3
> a
#(NIL 3 NIL)
> (aref a 1)
3
> (defstruct foo bar)
FOO
> (setq a __(make-foo)__)
#s(FOO :BAR NIL)
> (foo-bar a)
NIL
> (setf (foo-bar a) 3)
3
> a
#s(FOO :BAR 3)
> (foo-bar a)
3
__Setf 是唯一可以用来设定结构里数据域位的值以及设定数组里元素之值的方法。__
下面是跟 setf 及相关的函数调用的一些范例:
> (setf a (make-array 1)) ; setf 作用在单一个变量上面的效果跟 setq 一样。
#(NIL)
> __(push 5 (aref a 1)) __ ; push 也可以拿来当作是 setf 使用(不过参数顺序不太一样喔!)
(5)
>__ (pop (aref a 1)) __ ; 既然 push 可以存值,那 pop 当然就可以取值。
5
> (setf (aref a 1) 5)
5
> (incf (aref a 1)) ; incf 的功用是**从内存位置读取出值,然后累加**
6 ; 最后在把累加完之后的值,存回到相同的内存位置。
> (aref a 1)
6
===== 布尔值与判断条件 =====
Lisp 使用其值为本身的 NIL 表示“假”。__任何其它不是 NIL 的值都表示真__。然而除非有特殊理由要这样处理不然我们还是会习惯上利用其值为本身的 T表示“真”。
Lisp 提供了一系列的标准的逻辑函数比如像是__ and, or 以及 not 函数__。and 以及 or 函数是属于 short-circuit ,也就是说,如果 and 函数的有任何一个个参数的运算结果已经是 NIL ,拿之后的参数将不用进行运算估值;而 or函数如果有任何一个参数运算结果事 T ,那之后的参数就不会进行运算估值。
Lisp 也提供了几个__特殊窗体__用来做控制判断执行的条件。最简单的就是 if 语句,在 if 语句的第一个参数将会决定,接下来执行的会是第二个或是第三个参数。
> (if t 5 6)
5
> (if nil 5 6)
6
> (if 4 5 6)
5
如果你在 if 语句之后的 then(第二个参数) 或是 else(第三个参数) 的部分想要__执行超过一个以上的语句__那你可以使用 __progn 这个特殊窗体__。 progn 将会执行在它内部的每一个语句并且返回__最后一个__评估值之后的结果。
> (setq a 7)
7
> (setq b 0)
0
> (setq c 5)
5
> (if (> a 5)
** (progn**
** (setq a (+ b 7))**
** (setq b (+ c 8)))**
(setq b 4))
13
if 语句**如果缺乏 then(第二个参数) 或是 else(第三个参数) 的部分**,其实也可以用 __when 或是 unless __特殊窗体改写如下范例
> (when t 3)
3
> (when nil 3)
NIL
> (unless t 3)
NIL
> (unless nil 3)
3
when 跟 unless 特殊窗体并不像 if 只可以放一个语句他们__可以放任一个数的语句在他们内部当作参数__。(例如: (when x a b c) 就等价于 (if x (progn a b c)) 。 )
> (when t
(setq a 5)
(+ a 6))
11
更复杂的控制判断条件可以透过__ cond 特殊窗体__来处里 cond 特殊窗体相当于if ... else if ... fi 控制判断条件一样。
cond 特殊窗体包含有开头的 cond 字符,后面接的一连串的**判断子句,每一个判断句都是一个链表**,该链表的第一个元素就是判断条件,而剩下的元素(如果**有的话)就是要有可能要执行的语句。 cond 特殊窗体会找寻第一个满足判断条件为真(也就是,不是 NIL)的子句,然后执行该子句里面对应的语句,并且**把运算评估完的结果当作是返回值。而剩下的其它子句就不会被运行评估了, cond 特殊窗体只会运行__至多一个__符合判断结果为真的子句语句。如下范例
> (setq a 3)
3
> (cond
((evenp a) a) ;如果(if) a 是偶数,则返回值为 a
((> a 7) (/ a 2)) ;不然,如果(else if) a 比 7 大,则返回值为 a/2
((< a 5) (- a 1)) ;不然,如果(else if) a 比 5 小,则返回值为 a-1
__(t 17)__) ;不然(else),返回值为 17
2
如果在 cond 特殊窗体里面,判断条件为真且要执行的那个子句,并没有要执行的语句部分的话,那 cond 窗体就会返回判断条件为真的那个结果。如下:
> (cond __((+ 3 4) )__)
7
接下来是一个用到 cond 特殊窗体的**递归函数定义**的巧妙小例子。你或许可以试着证明看任何 x 以比 1 大的整数值带入,最后这个递归函数都会终止。(如果你成功证明出来了,请务必要昭告天下!) (译注:这是数学界有名的 3x+1 猜想,至 2006 年目前依然无人成功证出。)
> (defun hotpo (x steps) ; hotpo 会把偶数减半,把奇数乘三后加一
(cond
((= x 1) steps)
((oddp x) (hotpo (+ 1 (* x 3)) (+ 1 steps)))
(t (hotpo (/ x 2) (+ 1 steps)))))
A
> (hotpo 7 0) ; 从 7 经 hotpo 运算到 1 共要经过 16 步。
16
Lisp 也有一个 __case 语句__就类似 C 语言的 switch 语句句一样。如下范例:
> (setq x 'b)
B
> (case x
(a 5) ; 如果 x 是 a ,那返回值就是 5
((d e) 7) ; 如果 x 是__ d 或 e __那返回值就是 7
((b f) 3) ; 如果 x 是 b 或 f ,那返回值就是 3
(__otherwise__ 9)) ; 此外,那返回值就是 9
9
最后的 otherwise 子句,所表示的意思是"如果 x 不是 a, b, d, e, 或是 f ,那返回值就是 9 。″
===== 迭代结构 =====
在 Lisp 中最简单的迭代结构(Iteration)就是 __loop(循环) __了 loop 结构会一再重复执行其内部的指令,直到执行到 __return 特殊窗体__才会结束。如下范例
> (setq a 4)
4
> (loop
(setq a (+ a 1))
(when (> a 7) (return a)))
8
> (loop
(setq a (- a 1))
(when (< a 3) (return)))
NIL
下一个最简单的迭代结构就是 __dolist__ dolist 会__把变量依序绑值于链表里面的所有元素直到把达到链表底部没有元素才结束__。如下范例
> (dolist (x '(a b c)) (print x))
A
B
C
NIL
__Dolist 的返回值必定是 NIL __。请注意看上面范例里面 x 绑订的值却从未是 NIL ,在 C 后面的 NIL 是 dolist 的返回值,也就是要满足 "读取—评估—显示″循环必定会显示的评估(运算)值。
最复杂的迭代结构主要就是 __do 循环__了。一个 do 循环的范例看起来就像下面这样:
> (do ((x 1 (+ x 1))
(y 1 (* y 2)))
((> x 5) __y__)
(print y)
(print 'working))
1
WORKING
2
WORKING
4
WORKING
8
WORKING
16
WORKING
32
在上面范例里面,在 do 循环的后面的__第一个大区块里的是变量名称以及该变量绑定的初始值还有每次循环运行一圈之后变量的更新条件__。第二个大区块里的则是__ do 循环的终止条件,以及 do 循环结束之后的返回值__。(译注:此终止条件是在每次进入循环主体前检查,也就是循环主体可能会连一次都没有被执行到。)最后一个大区块则是__循环主体__。do 窗体会先如同 let 特殊窗体依样绑定变量初始值,然后检查循环终止条件是否成立,只要每次检查终止条件不成立,那就会执行循环主体,然后再回到检查终止条件地部分,直到检查到**终止条件成立**,则返回当初在第二大区块的所指定的返回值。
另外还有一个__ do* 窗体__功能如同上面的 do 窗体,只是相对于把上面语句的 let 改成let* 而已。
===== 无定位返回 =====
前一节中迭代示例里的return语句是一个无定位返回(Non-local Exits)的示例,另一个是 return-from它__从包围它的函数中返回指定值__。
> (defun foo (x)
**(return-from foo 3)**
x)
FOO
> (foo 17)
3
实际上return-from 语句可以__从任何已命名的语句块中退出__──只是默认情况下函数是唯一的命名语句块而已。我们可以用__ block语句自己定义一个命名语句块__。
> (block foo
(return-from foo 7)
3)
7
return 语句可以从任何nil命名的语句块中返回。__默认情况下循环是nil命名__而我们可以创建自己的nil标记语句块。
> (block nil
(return 7)
3)
7
另外一个无定位退出语句是 error 语句:
> (error "This is an error")
Error: This is an error
The error form applies format to its arguments, then places you in the debugger.
error语句格式化它的参数然后进入**调试器**。
===== Funcall,Apply与Mapcar =====
在本文前半块,我曾说过要给几个**把函数名称当作是函数传入参数**的例子。举例如下:
> (funcall __#'+__ 3 4)
7
> (apply #'+ 3 4 '(3 4))
14
> (mapcar #'**not** '(t nil t nil t nil))
(NIL T NIL T NIL T)
funcall 会调用以第一个参数为名的函数,并把 funcall 的其它参数当作是要调用的函数的传入参数。
apply 就像是 funcall 一样的功用,除了 apply 的__最后一个参数必须要是链表__这最后链表里面的元素就像是在使用 funcall 时的__额外参数__一样。
mapcar 的第一个参数必须是可以作用于__单一传入值__的函数名称 mapcar 会把该函数名称套用在__其后参数链表的每一个元素上__并且把函数调用结果集合起来形成__新的链表__回传。
funcall 跟 apply 就是因为他们的**第一个参数可以是变量**,所以特别有用。举例应用如,当一个搜索引擎可以采用启发式的函数当作是参数,并且利用 funcall 或 apply 把那个函数参数作用在状态语句上。稍后会介绍的排序函数,也是利用 funcall 来传递排序时要用的哪个比较函数来比较大小。
mapcar 跟__匿名函数__(后面会介绍)一起使用,可以取代很多循环的使用。
===== 匿名函数 =====
如果你想要创造一个__暂时性使用的函数__并且不想烦恼应该给那个函数什么名称此时就可以使用匿名函数(lambda)。
> __#'__(lambda (x) (+ x 3))
(LAMBDA (X) (+ X 3))
> (funcall * 5) ; 译注: *** 表示前一个输入窗体**,在此就是 #'(lambda (x) (+ x 3))
8
把 lambda 跟 mapcar 一起组合使用可以取代大多数的循环。如下范例,下面的两个窗体是等价的。
> (do ((x '(1 2 3 4 5)__ (cdr x)__)
(y nil))
(__(null__ x) (reverse y))
(push (+ (car x) 2) y))
(3 4 5 6 7)
>** (mapcar **__#'__**(lambda (x) (+ x 2)) '(1 2 3 4 5))**
(3 4 5 6 7)
===== 排序 =====
Lisp 提供了两个主要的排序(Sorting)函数: sort 跟 stable-sort 。
> (sort '(2 1 5 4 6)__ #'<__)
(1 2 4 5 6)
> (sort '(2 1 5 4 6) #'>)
(6 5 4 2 1)
sort 的第一个参数是一个链表而第二个参数则是一个比较大小用的__比较函数的名称__。sort 函数并不保证排序的稳定性,也就是说,如果有两个元素 a 与 b 满足(and (not (< a b)) (not (< b a))) sort 或许有可能在排序之后,会对调 a 与 b 的顺序。而 stable-sort 跟 sort 使用方式完全一样,除了 stable-sort 保证对于相同的元素必定不会对调顺序。
请务必注意: sort 允许破坏他的输入参数序列,所以如果原始传入参数对你而言是很重要的,请先利用 copy-list 或 copy-seq 做好备份。
===== 相等 =====
Lisp 对于“相等(Equality)”的意义有很多种类型。 **数值上的相等**是用__ = __来判别。两个字符则是用__ eq__ 来检查他们是否是同一个。两个有相同值的链表拷贝并不是 eq 的(译注:不同的内存位置)但这两个有相同值的链表拷贝却是__ equal__ 的(译注:储存的数据是一样的)。
> (eq 'a 'a)
T
> (eq 'a 'b)
NIL
> (= 3 4)
NIL
> (eq '(a b c) '(a b c))
__NIL__
> (equal '(a b c) '(a b c))
__T__
> (eql 'a 'a)
T
> (eql 3 3)
T
__eql __判断式等价于 "判断是否是相同类型" 加上 "如果同是字符,判断是否 eq " 再加上"如果同是数值,判断是否 = "的合体。
> (eql 2.0 2)
NIL
> (= 2.0 2)
T
> __(eq __12345678901234567890 12345678901234567890)
NIL
>__ (=__ 12345678901234567890 12345678901234567890)
T
> (eql 12345678901234567890 12345678901234567890)
T
用在 字符跟数值上, equal 判断式就等价于 eql 。对于两个 cons 而言,如果他们的 car跟 cdr 都是 equal ,那这两个 cons 就是 equal 的。对于两个 structures (结构) 而言,如果他们有相同的数据类型,并且相对应的数据域位是 equal 的,那这两个结构就是 equal 的。
===== 一些有用的链表函数 =====
下面是一些用来操作链表的有用函数。
> __(append__ '(1 2 3) '(4 5 6)) ; 连结许多链表
(1 2 3 4 5 6)
> __(reverse__ '(1 2 3)) ; 反转一个链表里面的元素
(3 2 1)
> (member 'a '(b d a c)) ; 集合元素的"属于"判断 -- 它会返回第一个找到的元素
(A C) ; __至后方所有元素所形成的链表__也就是找第一个 car 是该元素的链表
; 译注空链表NIL 即为假,其它任何非空链表皆表示真。
> (find 'a '(b d a c)) ; 另一个__检查元素是否属于该集合__的方法就是用 find 。
A
> (find '(a b) '((a d) (a d e) (a b d e) ())__ :test #'subsetp__)
(A B D E) ; find 是很有弹型的,可以传入**要用来判断的函数**。
; 上面例子就是改用 subsectp (检查是否为子集合) 来找寻满足条件的集合。
> (subsetp '(a b) '(a d e)) ; 检查是否为子集合
NIL
> (intersection '(a b c) '(b)) ; 求集合的__交集__
(B)
> (union '(a) '(b)) ; 求集合的__联集__
(A B)
> (set-difference '(a b) '(a)) ; 求差集合
(B)
Subsetp, intersection, union, 和 set- difference 都有一个基本假设就是传入值的__参数链表内不会有重复的元素__(也就是集合),不然的话,像是 (subsetp '(a a) '(a b b)) 判断出来的返回值就可能是假。
Find, subsetp, intersection, union, 和 set- difference 都可以加上__ :test 这一个 keyword 参数__用以改变判断条件而如果没有使用 :test 改写判断条件的话,预设就是使用 **eql **当作是判断条件。
===== 从 Emacs 开始 =====
你可以使用 Emacs 编辑 LISP 代码Emaces 在打开__ .lisp __文件时总会自动进入 LISP 模式,不过如果我们的 Emacs 没有成功进入这个状态,可以通过输入 **M-x lisp-mode **做到。
我们也可以在 Emacs 下运行 LISP先确保在我们的私人路径下可以运行一个叫 "lisp" 的命令。例如,我们可以输入:
ln -s /usr/local/bin/clisp ~/bin/lisp
然后在 Emacs 中输入 __M-x run-lisp__ 。我们可以向 LISP 发送先前的 LISP 代码,做其它很酷的事情;在 LISP 模式下的任何缓冲区输入 C-h m 可以得到进一步的信息。
实际上我们甚至不需要建立链接。Emacs 有一个变量叫 inferior-lisp-program ;所以我们可以把下面这行
(setq__ inferior-lisp-program__ "/usr/local/bin/clisp")
输入到自己的 .emacs 文件中Emacs 就会知道在你输入 M-x run-lisp 时去哪里寻找 CLISP。
Allegro Common LISP 对使用 Emacs 有一个在线手册。要使用它,将下面内容添加到你的 .emacs 文件中:
(setq load-path
(cons "/afs/cs/misc/allegro/common/omega/emacs" load-path))
(autoload 'fi:clman "fi/clman" "Allegro Common LISP online manual." t)
然后命令 M-x fi:clman 将提示你 LISP 主题并输出相应的文档。

View File

@@ -0,0 +1,234 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-01T14:03:10+08:00
====== EMACS LISP 基本知识小结 ======
Created Wednesday 01 February 2012
http://forum.ubuntu.org.cn/viewtopic.php?f=88&t=332316
这些是我最近两天为了理解定制Emacs 而学习的LISP编程基本知识贴来供大家一览。
[本文内容选自 <Emacs lisp 入门>]
主要是给出关于 Emacs & LISP 的一个初步的感性直观认识。
官方文档地址: http://www.gnu.org/software/emacs/manua ... index.html
Programming in Emacs Lisp:
http://www.gnu.org/software/emacs/emacs ... intro.html
===== [1] 按键方法 =====
C-x: 表示按键 Ctrl+x ; M-x 表示 Alt+x
按键序列: C-x f: 先按组合键 Ctrl+x, 松开后再按 f 字母键即可(注意回显区的内容显示)。
键和弦: 在书中提到这个词,感觉很不错。用一系列的快捷键序列来进行操作,那种感觉跟在钢琴上击键,何尝不是一种快感!
===== [2] EMACS 的 LISP 相关快捷键 =====
LISP 表达式求值: 将光标移到LISP表达式的右括号外按 __C-x C-e__
安装自定义的 LISP 函数:
(1) 在函数定义后的右括号外执行 C-x C-e 即可 [只能供本次使用,下次打开时又要安装]。
(2) 写在一个文件里,然后在 .emacs 中加载。
比如建立一个 myfun.el 文件, 将函数定义写在里面, 然后在 ~/.emacs 中加载。
(**load** "~/csat/lisp/myfun.el")
可以在打开时 C-h f ---> Describe function: 输入函数名, 验证是否确实已经安装。
查看 LISP 函数说明: **C-h f** ---> Describe function: 输入函数名
查看 LISP 变量说明: **C-h v** ---> Describe variable: 输入变量名
[
例如,要查看 concat 的函数说明,可以按 Ctrl+h f,
回显区会显示 Describe function: 在冒号后输入 concat 即可。
]
在 emacs 中查看源代码:
(1) 建立 TAGS 文件, Emacs 根据 TAGS 文件来查找源代码
例如, find ~/csat/C/ -name "*.c" -print | etags -
将目录 /home/shuqin1984/csat/C 下的所有 .c 文件建立 TAGS
此时将在 ~/ 下生成 TAGS 文件(ls 命令可查看结果)
(2) 加载 TAGS 文件: 在 ~/.emacs 中加入
;;;; tags files
(setq tags-file-name "~/TAGS")
(3) 在 Emacs 中 按 M+. , 然后按提示输入函数名
可以为不同语言的源代码建立不同的TAGS文件然后进行加载
===== EMACS 工作方式: =====
[1] 缓冲区文件与持久性文件:
当打开一个持久性文件进行编辑时Emacs 将会为这个文件创建一个名字几乎相同的缓冲区文件在Emacs作出的修改都是对该缓冲区文件的修改而不是原文件。只有当进行保存时才会将缓冲区文件中的修改存入原文件。
[2] EMACS 执行命令:
当键入一个命令,比如 C-x C-f 时, **Emacs 将查找键绑定的函数**(可能用C或LISP写成)必要的时候还需要输入参数。在输入参数之后按回车键Emacs将执行该命令并在**缓冲区文件**中显示执行结果(缓冲区文件就是你正打开的正编辑的文件,它并不是最终被保存的持久文件)。
例如 按 C-x b 时,将会调用 switch-to-buffer 函数,并在回显区显示:
switch to buffer (default *缓冲区名*): 键入缓冲区文件名即可。
-----------------------------------------------------------------------
===== 1. LISP 合法表达式: =====
LISP 合法表达式可以是如下情况:
A. 不带括号的原子常量或变量;
B. 紧跟单引号的列表元素;
C. 带括号的列表: (函数名 参数1 参数2 ... 参数N)
其中参数可以是 A,B,C 三种形式中的任一种(__不能是函数名__)。
很显然LISP表达式含有嵌套和递归的特性可以写出任意层嵌套的复杂表达式只要记住合理地使用括号。
下面是一些合法的表达式(有错误的都标识出来了)[可以直接在表达式最后面按 C-x C-e 查看回显区结果]
; __LISP 原子常量__ 数值,字符串(带__双__引号的文本),紧跟单引号的列表
3.1415926 [在这里按 C-x C-e 查看结果]
"i ahadd"
("i ahadd") ; __ERROR__ 不紧跟 ' 号的列表第一个符号必须是已定义的__函数名__
'("i ahadd")
'(one two three four)
'(this list has (a list inside of it)) [在这里按 C-x C-e 查看结果]
__; 求值__非常类似于前缀表达式
(* (+ 2 3) (- 1 3))
'(* (+ 2 3) (- 1 3)) ; 注意,这是一个文本
; __全局变量定义__ set setq
(set__ '__PI 3.1415926) ; 第一个变量符号必须紧跟单引号 '
PI
(setq E 2.71) ; 第一个变量会**自动加上**单引号 '
E
; __局部变量定义__ let
(let
((person 'me)
(dream '(a house))
)
(message "%s dream is %s." person dream)
person ; Error: person 未定义
; 一个计数器
(setq counter 0)
(setq counter (+ counter 1))
counter
fill-column ; EMACS__ 内建变量__
(* 2 fill-column)
(fill-column) ; ERROR__ fill-column 是未定义的函数__
(this is an unquoted list) ; Error** this 是**__未定义的函数__
(error info) ; Error** info 是**__未定义的变量__
; __内建函数 message__ 类似于 C 的 prinf
(message "the name of this buffer is %s." (buffer-name))
(message "the buffer is %s." (current-buffer))
(message "the name of this buffer is %s." (buffer-file-name))
(message "the value of this fill-column is %d." fill-column)
; buffer-size, point 等都是内建函数,只是**不需任何参数列表**
(other-buffer)
(buffer-size)
(point)
(point-max)
(point-min)
(other-buffer)
(switch-to-buffer (other-buffer))
; 函数定义
(defun multiply (x y)
"将给定的两个数相乘"
(* x y)
)
(multiply 3 5)
; if 测试
; (if (expr) (action-if-true) __(action-if-false)[可选]__)
__; LISP nil为假 非 nil 为真__
(if () 'true 'false) ; __空列表() 视为假__
(if (- 1 1) 'true 'false) ; __零 非假,因为它不是空列表,而是 0__
; while 测试
; (while (expr) (action1-if-true) (action2-if-true) ... (actionN-if-true))
(let ((i 10) (result __""__))
(while (>= i 0)
(setq result (__append__ result (__list__ i)))
(setq i (__1-__ i))
)
(message "result = %s." result)
)
; __cond 测试__
; (cond
; (expr1 action1-if-true)
; (expr2 action2-if-true)
; ...
; (exprN actionN-if-true)
;
; )
; 类似于 switch-case
(defun signof (num)
"测试给定数的符号"
(let ((sign))
(cond
((> num 0) (message "%d is a positive." num))
((eq num 0) (message "%d is zero." num))
((< num 0) (message "%d is a negative." num))
)
)
)
(signof 1)
(signof -1)
(signof 0)
__; 交互函数定义__
(defun IsGreaterThanZero(num)
"测试是否给定参数是否大于零"
(__interactive __"p")
(if (> num 0)
(message "%d is greater than 0. " num)
(message "%d is not greater than 0." num)
)
)
; 可以作为非交互函数调用
(IsGreaterThanZero 0)
(IsGreaterThanZero 1)
(IsGreaterThanZero -1)
; 调用内建函数
(__concat __"abc" "123")
(concat "oh" (list 1 2) "god!" )
等价于:
(concat "oh" **'(1 2)** "god!")
(__substring__ "hei, look!" 5 9)
(concat "hei, " (substring "hei, look!" 5 9) "!") ; 嵌套表达式
; 带任意数量参数的函数
(*) (* 3) (* 1 2 3 4 5)
(+) (+ 3) (+ 1 2 3 4 5)
(concat) (concat "1") (concat "1" () "(+ 33 44)")
; __参数类型出错 __hello 必须是数值
(+ "hello" 2) ; ERROR
; 这是给出的错误消息
Debugger entered--Lisp error: (wrong-type-argument number-or-__marker__-p "hello")
; 递归函数
(defun refac(num)
"递归计算阶乘 n! = 1 * 2 * ... * n"
(interactive "p")
(if (eq num 0) 1
(* (refac(1- num)) num)
)
)
(defun printfac(num)
"打印阶乘值"
(interactive "p")
(message "%d! = %d." num (refac num))
)
(refac 0)
(refac 1)
(refac 3)

View File

@@ -0,0 +1,126 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-01T17:01:06+08:00
====== Emacs lisp 基础 ======
Created Wednesday 01 February 2012
http://zhengdong.me/2010/10/25/emacs-lisp-basics/
使用 Emacs 大概也有大半年了越用越觉得它的强大始终都有惊喜每次看到一个功能心中想Emacs 可以吗?答案真的往往会是 Yes!
1: (defun factorial (n)
2: (if (<= n 1)
3: 1
4: (* n (factorial (- n 1)))))
上面这种充满括号的奇怪的语言叫 Lisp它是仅次于 Fortran 的最老的高级程序设计语言,名称来源于 LISt Processing它有__两个方言__分别是 Common Lisp 和 Scheme。 Emacs lisp (elisp) 来源于 Common Lisp。使用 Emacs如果不能自己打造适合自己的功能虽然仍然可以享受 Emacs 带来的流畅和便利,但却会与另一种乐趣失之交臂,这篇文章简要叙述 elisp 的基本概念和语法。
===== Hello World =====
1: (message "Hello, World!")
使用 C-x C-e 执行,便可以从 minibuffer 中看到这段代码的输出,简单吧?
===== 变量 =====
1: (set 'numstr 'nine)
2: (setq numstr 'nine)
set 和 setq 用来对变量赋值变量不需要事先声明而且__都是全局__的。
在 C 中”int x = 3;” 表示将 3 赋值给 x我们不能直接再使用 x 这个 symbol因为它已经代表了整型 3 或者代表之后重新赋的新值,如果需要使用 x那就只能重新定义 “char c = x而在 elisp 中__我们针对的就是 symbol__也就是说__可以同时使用 x 和 x 所代表的值__。比如上面代码set 语句中 number 和 nine 前面都有一个 (quote) 符号,这个符号到底做什么用呢?
它告诉 Lisp 解释器__不要去执行这个 symbol而是直接使用__。比如在 L1 执行完后,**numstr 代表的是 ninenumstr 仍然指的是 numstr**. 为了书写方便setq 免去了 ‘,作用同 set。
1: (let ((name __'Chris__)
2: (age __'26__))
3: (message "Name is %s, %d years old" name age))
let 用来定义在特定区域中的局部变量,还有两一种形式,如下
1: (let (name age)
2: (setq name 'Chris)
3: (setq age '26)
4: (message "Name is %s, %d years old" name age))
===== 运算 =====
1: (+ 1 2) ; = 3
2: (+ 1 2 3) ; = 6
3: (- 8 5) ; = 3
4: (* 2 3) ; = 6
5: (/ 7 2) ; = 3
6: (/ 7 2.0) ; = 3.5
7: (% 9 4) ; = 1
8: (expt 2 3) ; = 8
这里需要强调的是如果需要输入浮点数时,需要在数字后面加 .0,否则还是会被当整型处理的。如 L5 & L6。
===== 比较 =====
在 elisp 中__false 用 nil 来表示,空列表也用 nil 来表示,其余的都是 true__也就是说 0 和 空字符串也是 true。
1: (< 2 3) ; = t
2: (> 2 3) ; = nil
3: (<= 2 2) ; = t
4: (>= 2 3) ; = nil
5: (= 2 3) ; = nil
6: __(string=__ "he" "he") ; = t
7: __(string<__ "he" "ie") ; = t
8: __(not__ (< 3 2)) ; = t
9: (or (< 3 2) (< 2 3)) ; = t
10: (and (< 3 2) (< 2 3)) ; = nil
string< 比较的是字符串的字典顺序,没有 string>。
===== 表达式 =====
1: (if (< 3 2)
2: (message "3 < 2")
3: (message "3 > 2"))
4:
5: __(when__ (< 2 3)
6: (setq x 2)
7: (setq y 3)
8: (message "%d < %d" x y))
9:
10: (setq x 0)
11: __(while__ (< x 4)
12: (print "hit ")
13: (setq x (1+ x)))
14:
15: __(progn__
16: (setq x 2)
17: (setq y 3)
18: (setq z (+ x y)))
条件判断 ift 则执行下一句 L2nil 则执行第三句 L3when 表示__如符合条件就执行所有语句__while 为循环语句L10 打印出 4 个 hitprogn 表示__一个代码块__返回最后一个语句的返回值。
1: (setq x 9)
2: __(cond__
3: ((<= x 0) 0)
4: ((= x 1) 1)
5: ((> x 1) (+ x 1)))
cond 用来列出一系列条件,以及满足该条件时的动作,上面代码表示当 x<=0 时,返回 0当 x=1 时返回 1当 x>1 时,返回 x+1 的值。
===== 函数 =====
1: (defun show (type)
2: (message "This is a my first %s function" type))
3: (show **'demo**)
如果想要在 Emacs 中使用 __M-x 调用__的话需要加 (interactive)
1: (defun yeah ()
2: (interactive)
3: (message "yeah!"))
interactive 可以带很多参数,具体请参考。
lambda 用来定义__匿名函数__
1: ((lambda (number) (* 7 number)) 3)
在函数定义后加参数就可以调用该匿名函数了。

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,363 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-01T14:03:56+08:00
====== Emacs Lisp语言 ======
Created Wednesday 01 February 2012
http://www.neu.edu.cn/cxsj/online/C1/Lisp%E8%AF%AD%E8%A8%80.htm
An Introduction to Programming in Emacs Lisp
这本书 emacs 里有按“C-h i”输入“mEmacs Lisp Intro”就能看到。我把其中比较基础的部分挑出来翻译了一下。
Lisp 语言的历史
Lisp 语言最早是在 20 世纪 50 年代末由麻省理工学院MIT为研究人工智能而开发的。Lisp 语言的强大使它在其它方面诸如编写编辑命令和__集成环境__等显示其优势。而 GNU Emacs Lisp 主要由 Maclisp 发展而来,该语言由 MIT 在 20 世纪 60 年代写成。它在某种程度上继承了 Common Lisp而 Common Lisp 在 20 世纪 80 年代成了一种标准。但是 Emacs Lisp 要比 Common Lisp 简单得多。
===== 表处理 =====
Lisp 代表 **LISt Processing**,即表处理,这种编程语言用来处理由括号(即“(”和“)”)构成的列表。括号标记着表的边界,有时候一个列表之前加了一个撇号(即“'”或者说是单引号。__列表是 Lisp 语言的基础__。在 Lisp 语言中,一个列表看起来像这样:“'(rose violet daisy buttercup)”。也可以写成以下这种形式:
* '(rose
* violet
* daisy
* buttercup)
这样看起来更为熟悉。这个表的元素是四种花的名字rosevioletdaisy 和 buttercup。列表中也可以包含__数字__如“'(1 2 3)”这种形式。在 Lisp 语言中,**数据和程序用同一种方式表示**就是说它们都是由__符号(symbol)、数字或者其它表__组成的中间用空格分开两边加上括号。由于一个程序看起来像数据它可以很容易地在其它地方用作数据。这是 Lisp 语言一个非常强大的特性。括号内带有分号和句号(即“;”和“.”)的句子不是列表。这里列举了另外一种形式的列表:
'(this list has (a list inside of it))
该表的元素包括 thislisthas 和一个子表 (a list inside of it)。这个子表由元素 alistinsideofit 组成。
===== 原子 =====
在 Lisp 语言中,我们**将单词(word)叫做“原子”(atom)**因为原子是不可分的。__一个列表可以由一个或多个原子组成或者一个都没有__不包含任何原子的列表形如“()”也可以写作__“nil”__被称为“空表”。空表既可看作是一个原子也可以看作是一个列表。
__原子和列表都被称为“符号表达式” symbolic expressions__ or, more concisely, __s-expressions__。另外由双引号标记的文本甚至可以是句子或者段落也是一个原子
'(this list includes "text between quotation marks.")
这种形式的原子,即 "text between quotation marks." 也叫__字符串(string)__。
另外__以单引号开头的list也是原子__。
常见的atom包括number, string, symbol如+、-、*、/、flower、name等**与函数或变量绑定的单词**, quoted list.
__Lisp programming is mostly about symbols__ (and sometimes numbers) within lists.
__a list is between parentheses, a string is between quotation marks, a symbol looks like a word, and a number looks like a number.__
===== 执行程序 =====
Lisp 语言中的__一个表就是一个可执行的程序__。如果你运行它用 Lisp 术语来说就是“求值”),计算机会按如下三种情况中的一种执行:
* 一是什么也不做仅仅__返回列表本身__
* 二是返回错误信息
* 三是将列表中的第一个符号作为一个命令进行一些操作。
如果在一个列表前加上“'”,当计算机处理该列表时就执行第一种情况,如果列表前没有“'”,则表中第一项具有特殊意义,它是一种命令(在 Lisp 语言中这些命令被称为__函数__。比如
(+ 2 3)
这个列表执行(用术语来说就是“对列表求值”)的结果是 5因为该表的第一项是“+”,表示对表中剩下的元素进行求和。如果在 Emacs 中执行只需要将光标移动到右括号后方然后执行“C-x C-e”即可在回显区可以得到结果“5”。这样的方式是将命令递交给程序在 Emacs 中被称为“Lisp 解释器”)的过程。
__evaluating a symbolic expression(原子或列表) most commonly causes the Lisp interpreter to return a value and perhaps carry out a side effect; or else produce an error.__
__将单引号放在list或symbol前面表示不对这个list或symbol求值而是返回紧跟引号的 printed representation。__
You can also__ evaluate an atom that is not part of a list__—one that is not surrounded by parentheses;
__可以直接执行atom__如number, stringsymbol (**代表函数名的symbol不能直接执行**必须要放在list中执行。)。
In the expressions, the parentheses tell the Lisp interpreter to treat buffer-name and buffer-file-name as __functions__; without the parentheses, the interpreter would attempt to evaluate the symbols as __variables__.
-> (set 'adf "dsf")
"dsf"
-> __adf__
"dsf"
->(defun demo ()
-> (message "demo")
->)
demo
->demo
**Debugger entered**--Lisp error: (__void-variable__ demo)
->__(demo)__
"demo"
->23
23
->"jdsf"
"jdsf"
->__'df__
df
当在交互式模式中执行lisp代码出现错误时emacs会自动进入调式模式
The error message is generated by__ a built-in GNU Emacs debugger__. We will `enter the debugger'. You get out of the debugger by typing __q__.
---------- Buffer: *Backtrace* ----------
**Debugger entered(表示进入了调式起模式窗口)**--Lisp error: (void-variable kk)
eval(kk)
eval-last-sexp-1(t)
eval-last-sexp(t)
eval-print-last-sexp()
call-interactively(eval-print-last-sexp nil nil
---------- Buffer: *Backtrace* ----------
You read the *Backtrace* buffer __from the bottom up__; it tells you what Emacs did.
===== 对子表求值 =====
如果对一个包含另外一个子表的列表进行求值外部列表可以利用内部子表返回的值进行求值。这也解释了为什么__内部表达式先被求值__因为这些返回值需要被外部表达式利用。如
(+ 2 (+ 3 4))
执行上一列表可在回显区得到结果 9因为 Lisp 解释器先在对内部表达式“(+ 3 4)”进行求值,得到 7再对外部表达式“(+ 2 7)”进行求值得到 9。
===== 变量 =====
在 Emacs Lisp 中__一个符号(symbol)可以指代一个值(value)也可以指代一个函数定义__。两者是有区别的函数定义是一套计算机需要执行的指令而一个值则是不同于其它事物的东西比如一个数或者一个名字。一个符号的值可以是任何 Lisp 表达式通常也被称为“变量”。一个符号还可以同时指代一个值和一个函数定义。__当一个符号与一个值绑定时可以单独求值(例如作为函数的参数)但是当一个符号与一个函数定义绑定时必须将其作为list的第一个元素才能调用该函数。__
===== 参数 =====
参数是指需要提交给函数的信息。不同的函数需要不同的__参数个数__一些函数甚至不需要参数另外不同的函数可能还需要不同的__参数类型__。例如
(concat "abc" "def")
列表中“concat”函数将两个或多个字符串连接起来形成一个字符串此函数所带的参数就是字符串对该列表求值得到结果“abcdef”。一些函数如“concat”“+”或者“*”可以有任意个数的参数,例如没有参数(即 0 个参数):
(+)
(*)
对以上两个表达式求值,分别得到 0 和 1。与此类似的还有“message”函数它用来给用户发送消息
(message "My name is %s and I am %d years old." "Xiaodong Xu" 25)
对上表求值得到“My name is Xiaodong Xu and I am 25 years old.”。其中双引号“"”中的“%s”并不直接显示而是搜寻该字符串后面的参数它对第二个参数“Xiaodong Xu”求值并将它的值显示在“%s”所在的位置。同理“%d”用第三个整数参数 25 来代替。
In Lisp, the arguments to a function are __the atoms(如number, string, variable, quoted list, quoted symbol) or lists__ that follow the function. __The values returned by the evaluation of these atoms or lists are passed to the function__. Different functions require different numbers of arguments; some functions require none at all.
当传递错误的参数类型时emacs返回如下错误
---------- 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* ----------
The first part of the error message is straightforward; it says **wrong type argument**. Next comes the mysterious jargon __word number-or-marker-p. This word is trying to tell you what kind of argument the + expected.__
The symbol number-or-marker-p says that the Lisp interpreter is trying to determine whether the information presented it (the value of the argument) is __a number or a marker__ (**a special object representing a buffer position**). What it does is test to see whether the + is being given numbers to add. It also tests to see whether the argument is something called a marker, which is __a specific feature of Emacs Lisp__. (In Emacs, **locations in a buffer are recorded as markers**. When the mark is set with the C-@ or C-<SPC> command, its position is kept as a marker. **The mark can be considered a number**—the number of characters the location is from the beginning of the buffer.) In Emacs Lisp, + can be used to add the numeric value of marker positions as numbers.
The p of number-or-marker-p is the embodiment of a practice started in the early days of Lisp programming. The __p stands for `predicate'__. In the jargon used by the early Lisp researchers, **a predicate refers to a function to determine whether some property is true or false**.
So the p tells us that__ number-or-marker-p is the name of a function__ that determines whether it is true or false that the argument supplied is a number or a marker. Other Lisp symbols that end in p include zerop, a function that tests whether its argument has the value of zero, and listp, a function that tests whether its argument is a list.
Finally, the last part of the error message is the **symbol hello**. __This is the value of the argument that was passed to __+. If the addition had been passed the correct type of object, the value passed would have been a number, such as 37, rather than a symbol like hello. But then you would not have got the error message.
===== 为变量赋值 =====
有好几种方法可以给变量赋值。一种是用“set”或者“setq”函数另一种是用“let”函数。这种过程用术数来说就是__给一个变量“绑定”一个值__。用“set”函数赋值可用如下形式
(set 'flowers '(rose violet daisy buttercup))
对上表求值时,列表“(rose violet daisy buttercup)”将在回显区显示,这是“**set”函数返回的值**。作为一种__附带效应(side-effcet)__符号“flowers”被绑定到这个列表也就是说“flowers”这个变量被这个列表所赋值。另外这种过程也说明了 Lisp 解释器的一种附带效应即赋值是我们人所期望的主效应。__ every Lisp function must return a value if it does not get an error__, but it will only have a side effect if it is designed to have one.)
在实际应用中,几乎每个赋值语句的第一个参数都需要加上撇号“'”由于这种情况很常见set 和第一个参数前的“'”组合起来构成了一个特定的形式“setq”
(setq carnivores '(lion tiger leopard))
这跟
(set 'carnivores '(lion tiger leopard))
完全等价。
“setq”也可用来__把不同的值赋给不同的变量__第一个参数被赋给第二个参数的值第三个参数被赋给第四个参数的值以此类推比如要把一组“trees”赋给“trees”符号同时把一组“herbivores”赋给“herbivores”符号可以这样做
(setq trees '(pine fir oak maple)
herbivores '(gazelle antelope zebra))
这里用了“赋值”这个词语另一种思维方式是“setq”把这个符号“指向”了这个列表。后者非常通用。
===== 函数 =====
除了一些用 C 语言写的“基本”函数,所有函数都是根据其它函数来定义的。定义函数时,可以在函数体中使用其它的函数,有些是用 Emacs Lisp 写的,有些是用 C 写的基本函数。用 C 写的目的是能够使 GNU Emacs 更容易在任何有 C 语言条件的计算机上运行。但是不必区分用 C 写的函数和用 Emacs Lisp 写的函数在使用上的区别。一个函数定义至多由五个部分组成: 1. 函数符号的名称。 2. 需要传递给函数的参数列表。如果没有参数,就是一个空表“()”。 3. 用于描述函数的文档(技术上可选,但是强烈建议写上)。 4. 可以使函数与人交互即可以用“M-x”加上函数名称或者用合适的键或键组合来调用函数的表达式可选。 5. 要求计算机执行的代码,即函数体。
函数定义可以写成如下模板:
(defun FUNCTION-NAME (ARGUMENTS...)
"OPTIONAL-DOCUMENTATION..."
(interactive ARGUMENT-PASSING-INFO) ; optional
BODY...)
举个例子定义一个乘以7的函数
(defun multiply-by-seven (number)
"Multiply NUMBER by seven."
(* 7 number))
对此表求值后我们就将“multiply-by-seven”这个函数装入 Emacs 了。此时再执行如下语句:
(multiply-by-seven 3)
在回显区可以得到结果 21。
“let”特殊形式
“let”表达式是 Lisp 语言的一种特殊形式在大多数函数定义中都会被用到。“let”用于防止混淆它创建一个“局部变量”的名字这个名字将覆盖“let”表达式之外所有的同名变量。由“let”表达式创建的局部变量仅在“let”表达式内部保留对外部没有影响。“let”一次可创建多个变量并给每个变量创建一个初始值初始值可以是某个特定的值也可以是空“nil”。在“let”创建变量之后将执行“let”主体中的代码并返回最后一个表达式的值。“let”表达式是由三部分组成的列表第一部分是符号“let”第二部分是一个列表称为“变量列表”每个元素可以是一个符号或者是一个二元列表第一个元素是符号第三部分是“let”表达式主体。可以用以下的模板来说明“let”表达式
(let VARLIST BODY...)
如果变量列表由二元组列表组成这种情况比较常见“let”表达式的模板可以写成这样
(let ((VARIABLE VALUE)
(VARIABLE VALUE)
...)
BODY...)
下面给出创建并初始化两个变量“zebra”和“tiger”的表达式“let”表达式主体是一个调用“message”函数的列表。
(let ((zebra 'stripes)
(tiger 'fierce))
(message "One kind of animal has %s and another is %s."
zebra tiger))
“if”特殊形式
除“defun”和“let”之外还有条件“if”。这种形式用于让计算机做判断。“if”背后的基本涵义是“如果一个判断为真那么一个表达式将被求值”。如果判断为假表达式不会被求值。判断和执行部分是“if”列表的第二和第三部分而第一个元素是“if”。不过“if”表达式的判断部分常被称为“if部分”而执行部分常被称为“then部分”。 “if”表达式也可以包含第三个可选参数即“else部分”用于判断为假的情况。在这种情况下then 部分不会被求值,而 else 部分将被求值。这时“if”表达式可写成如下模板
(if TRUE-OR-FALSE-TEST
ACTION-TO-CARRY-OUT-IF-THE-TEST-RETURNS-TRUE
ACTION-TO-CARRY-OUT-IF-THE-TEST-RETURNS-FALSE)
举个例子,
(if (> 4 5) ; if-part
(message "5 is greater than 4!") ; then-part
(message "4 is not greater than 5!")) ; else-part
判断为假时表达返回“nil”。值得注意的是“nil”在 Emacs Lisp 中有两种含义,一种代表空表,第二种代表假,而任何非空的值都被认为是真。
基础函数
在 Lisp 语言中“car”“cdr”和“cons”是基础函数。“cons”函数用于构建列表“car”和“cdr”函数用于拆分列表。一个表的 CAR 就是该表的第一项,如:
(car '(rose violet daisy buttercup))
执行该表达式可得到“rose”。同理一个表的 CDR 是该表的剩余部分就是说“cdr”函数返回该表第一项后面的部分。如
(cdr '(rose violet daisy buttercup))
执行得到“(violet daisy buttercup)”。同“car”一样“cdr”既不会修改也不会从列表中删除元素而仅仅是返回一个值。这是一个非常重要的特性。 “cons”函数与“car”和“cdr”相反比如“cons”可以从一个三元表组成一个四元表
(cons 'pine '(fir oak maple))
执行后在回显区得到结果“(pine fir oak maple)”。“cons”生成一个新列表此列表中元素“pine”后面跟有原始列表中的元素“(fir oak maple)”。可以说“cons”将一个新元素放在一个表的开头或者说是将其压在一个表上方从而生成一个新的列表而不改变原来列表的值。 “length”函数可以得到一个表中元素的个数
(length (cons 'violet '(daisy buttercup)))
由于(cons 'violet '(daisy buttercup))返回一个三元列表,所以上表执行得到结果 3。空表的长度为 0。 “nthcdr”函数与“cdr”函数相关它所做的是对一个列表重复执行“cdr”多次。如
(nthcdr 2 '(pine fir oak maple))
得到结果“(oak maple)”。 “nth”函数返回一个列表的第 N 个元素,以 0 为起点,如:
(nth 1 '("one" "two" "three"))
得到结果“two”。 “setcar”和“setcdr”函数与“car”和“cdr”函数类似但是它们会改变原有列表的值。
列表的实现
在 Lisp 语言中原子用直接的方式记录如果实际上不直接在理论上也是直接的。对一个表就另当别论了它是由一个指针对序列构成的。在这个序列中每个指针对的第一个指针指向一个原子或者另一个列表第二个指针指向另一个指针对或者指向空符号即“nil”用于标记列表的结束。一个指针本身是它指向的内容的地址所以一个表就是一个地址的序列。比如列表“(rose violet buttercup)”的实现可以用下图简单表示:
_ _ _ _ _ _
|___|___|--> |___|___|--> |___|___|--> nil
| | |
| | |
--> rose --> violet --> buttercup
当一个变量被诸如“setq”之类的函数赋给一个列表时它存储的是第一个长方形的地址。所以对表达式
(setq bouquet '(rose violet buttercup))
求值的结果可以这样表示:
bouquet
|
| _ _ _ _ _ _
--> |___|___|--> |___|___|--> |___|___|--> nil
| | |
| | |
--> rose --> violet --> buttercup
综合之前的概念,一个符号可以看作一个抽屉柜,用下图表示:
Chest of Drawers Contents of Drawers
__ o0O0o __
/ \
---------------------
| directions to | [map to]
| symbol name | bouquet
| |
+---------------------+
| directions to |
| symbol definition | [none]
| |
+---------------------+
| directions to | [map to]
| variable value | (rose violet buttercup)
| |
+---------------------+
| directions to |
| property list | [not described here]
| |
+---------------------+
|/ \|
循环和递归
Emacs Lisp 有两种主要的方式来重复求值一种用“while”循环另一种用“递归”。 “while”表达式主要有以下几种形式
简单形式
(while TRUE-OR-FALSE-TEST
BODY...)
空表判断循环
(while TEST-WHETHER-LIST-IS-EMPTY
BODY...
SET-LIST-TO-CDR-OF-LIST)
增量计数循环
SET-COUNT-TO-INITIAL-VALUE
(while (< count desired-number) ; true-or-false-test
BODY...
(setq count (1+ count))) ; incrementer
减量计数循环
SET-COUNT-TO-INITIAL-VALUE
(while (> counter 0) ; true-or-false-test
BODY...
(setq counter (1- counter))) ; decrementer
递归函数包含了 Lisp 解释器用来调用自身函数的代码,但是这些函数的参数有着细微的差别。用术语说就是有着不同的“实体”。
一个简单的递归函数形如:
(defun NAME-OF-RECURSIVE-FUNCTION (ARGUMENT-LIST)
"DOCUMENTATION..."
(if DO-AGAIN-TEST
BODY...
(NAME-OF-RECURSIVE-FUNCTION
NEXT-STEP-EXEXAMPLESSION)))
另一种形式是用“cond”函数
(cond
(FIRST-TRUE-OR-FALSE-TEST FIRST-CONSEQUENT)
(SECOND-TRUE-OR-FALSE-TEST SECOND-CONSEQUENT)
(THIRD-TRUE-OR-FALSE-TEST THIRD-CONSEQUENT)
...)
如果第一个判断条件返回空(即为假),第一个判断语句将被跳过,然后再对第二个判断条件求值,以此类推。如果某个判断条件返回真,该判断语句将被求值。

View File

@@ -0,0 +1,7 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-04T22:22:06+08:00
====== 程序员的elisp入门 ======
Created Saturday 04 February 2012

View File

@@ -0,0 +1,70 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-04T22:22:11+08:00
====== 1 ======
Created Saturday 04 February 2012
http://leenux.blog.edu.cn/2004/189842.html
偶来写一点东东,介绍一下一个已经会写程序的人(比如我自己),怎么理解
elisp这个东西。
想来想去ifcond这种东西就不介绍了偶相信那些在书里面花费若干
版面介绍ifcondloopwhenunless是什么意思该怎么用的都是
骗人钱财。
现在准备写的包括怎么用emacs自己带的帮助系统和elisp语言本身。
昨天晚上关于cons的是第一片。
* 操作入门
启动emacs之后这个被称为*scratch*的buffer可以拿来当作实验室。
只要在想要求值的符号之后输入C-x C-e求值的结果就会显示在屏幕下端
的mini buffer里面。如果输入C-u C-x C-e的话求值的结果会直接贴到
point当前在的位置。
无论什么模式下C-h f能够显示一个函数的doc文档C-h v能够显示一
个变量的doc文档。这对于写程序非常有帮助所以自己写的函数最好也
能够把doc文档写上将来自己或者别人都可能用的上。
* 基本概念
** elisp只有一种数据类型就是symbol。
所有能看的字符串都是symbol。所有symbol都可以求值。 所有的
symbol都是平等的不过其中有一些更平等。表示数字的symbol例如0,
12.3, 234, 求值得到的还是这个数字本身。表示字符串的symbol例如
"hello""world"求值得到的还是字符串本身。表示keyword的symbol
例如:foo:bar求值得到的还是keyword本身。更特别一点的t求值返
回的是tnil求值返回的是nil。最特别的是()求值返回nil因为()和
nil根本就是一回事情。
一个symbol__可以同时指代变量和函数__就是说一个foo即是变量又是
函数在lisp里面是合法的。
** 所有对symbol进行的操作被称为函数。
使用函数的方法是(function var1 var2 var3)。这个例子很显然的告诉
我们括号不能出现在symbol里面空格也不可以。但是例如*,/这样奇怪
的符号是可以的。
一个函数被调用的过程,被称为**对这对括号内部求值**。注意到之前对
symbol也叫求值这实际上暗示var1var2var3不一定是一个symbol
也有可能是其他的函数调用。如果整个程序就是这样的函数一层一层嵌套
得到的时候我们可以粗略的把它称为__函数式编程语言__。
(function var1 var2 var3)这样的一个函数,具体求值的过程,对于
elisp来说它__先求function的值__然后求每一个var的值最后将对var求
值得到的结果做为参数传递给function求值的结果。之所以强调先求
然后求而不是说从左到右依此求是因为对于括号中的第一个symbol是
特别照顾的。
我们至少可以总结出两点。我们对一个symbol求值时到底是希望得到
它代表的函数还是希望得到它代表的变量这__完全取决于这个symbol所__
__处的位置__。第二elisp中函数和变量并不是完全平等的你不能再象
scheme中那样了。即使你(setq func (lambda (...))),也无法直接
把它做为一个function来做因为setq就注定了func只能被当作一个变量
来处理而lisp只会试图去找func对应的函数。

View File

@@ -0,0 +1,45 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-04T22:28:33+08:00
====== 2 ======
Created Saturday 04 February 2012
** special form和macro
稍加尝试就会发现之前给出的function计算模型是有问题的。
比如(setq value 5)。如果value是一个从未被赋过值的symbol那在
setq调用之前整个计算过程就发生错误了。这是一个悖论啊。
如果上面这个例子还是比较容易搞定的话,这个就更难一点。
(if (< 3 2)
(message "true")
(message "false"))
如果elisp是纯粹的function programming language的话我们不用在
乎那两个message有没有被求值我们只要关心整个函数求值返回的结果就
对了。但是问题是__elisp不是纯粹的函数式编程语言它容许存在side__
__effect__上面的例子中我们关心的恰恰不是函数求值的结果而是它
的side effect。
当functional和imperative发生矛盾的时候我们必须有一点折衷的办
elisp的办法是对setq或者if一类的函数特殊处理所以它们被称为
__sepcial form__。Elisp Manual中有一节介绍elisp中所有的special form
其实也就寥寥几个。
而当我们想要扩展special form的时候比如我们想用cond来实现一个
自己的if的时候函数依然不能来插一腿。这个时候就会用到宏。
现在概念已经比较清楚了。最基本的function是整个elisp中最常见的形
__我们在emacs每次击键都会对某一个函数求值__(不信的话C-h k a看
看)可以说函数无处不在。special form虽然数量很少但是没有它们的
我们就没法在elisp里面简单的套用习惯的imperative的编程方法了
所以它们依然很重要。至于macro它可以帮助你把elisp改造的越来越象
某一门其他的语言。

View File

@@ -0,0 +1,50 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2012-02-04T22:35:42+08:00
====== 3 ======
Created Saturday 04 February 2012
http://leenux.blog.edu.cn/2004/189844.html
** list和lambda
List的存在是lisp得名的原因。list只有__三个基本操作__conscar
cdr。cons表示的是将一个symbol 添加到某个现有list的__表头__上去
car表示取的某个list的表头cdr表示取的某个list除了表头之外的剩余
部分。car和cdr是标准的lisp黑话没用过lisp系语言的人肯定不会知道
这两个词什么意思。
car和cdr很容易理解cons有一点特别。
lisp中list上每一个元素的结构等价于
struct node { void* data; void* next; }
如果用c来表示cons的作法的话大致是这样的
node* cons(arg1, arg2) {
list* = malloc (sizeof node);
list.data = arg1;
list.next = arg2;
return list;
}
所以我们如果做一次(cons 1 '(2 3))的话,想象一下,会组成一个新的
列表表头的data是1.但是如果我们做一次(cons '(2 3) 1)的话,会形成
一个新的表,表中的第一个元素是'(2 3),本应该指向第二个元素的位置,
现在存放了一个数字。这种结构被称为__dot pair(带点偶对)__emacs会把它
打印成((2 3) . 1)。dot pair一个常见用途是拿来做不定长参数列表(再
想一遍list的构造就明白这个设计实在很讨巧)。
Lambda本身就更没的说了。略过。
于是elisp的就没有别的特殊语法了。是不是太简单了