mirror of
https://github.com/beyondx/Notes.git
synced 2026-02-06 11:53:55 +08:00
Add New Notes
This commit is contained in:
912
Zim/Programme/lisp/Common_LISP_Hints.txt
Normal file
912
Zim/Programme/lisp/Common_LISP_Hints.txt
Normal 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 主题并输出相应的文档。
|
||||
234
Zim/Programme/lisp/EMACS_LISP_基本知识小结.txt
Normal file
234
Zim/Programme/lisp/EMACS_LISP_基本知识小结.txt
Normal 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)
|
||||
126
Zim/Programme/lisp/Emacs_lisp_基础.txt
Normal file
126
Zim/Programme/lisp/Emacs_lisp_基础.txt
Normal 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 代表的是 nine,而 ‘numstr 仍然指的是 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)))
|
||||
|
||||
条件判断 if,t 则执行下一句 L2,nil 则执行第三句 L3,when 表示__如符合条件,就执行所有语句__,while 为循环语句,L10 打印出 4 个 hit,progn 表示__一个代码块__,返回最后一个语句的返回值。
|
||||
|
||||
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)
|
||||
|
||||
在函数定义后加参数就可以调用该匿名函数了。
|
||||
10069
Zim/Programme/lisp/Programming_in_Emacs_Lisp.txt
Normal file
10069
Zim/Programme/lisp/Programming_in_Emacs_Lisp.txt
Normal file
File diff suppressed because it is too large
Load Diff
363
Zim/Programme/lisp/Programming_in_Emacs_Lisp/Emacs_Lisp语言.txt
Normal file
363
Zim/Programme/lisp/Programming_in_Emacs_Lisp/Emacs_Lisp语言.txt
Normal 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”,输入“m,Emacs 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)
|
||||
|
||||
这样看起来更为熟悉。这个表的元素是四种花的名字:rose,violet,daisy 和 buttercup。列表中也可以包含__数字__,如“'(1 2 3)”这种形式。在 Lisp 语言中,**数据和程序用同一种方式表示**,就是说它们都是由__符号(symbol)、数字或者其它表__组成的,中间用空格分开,两边加上括号。由于一个程序看起来像数据,它可以很容易地在其它地方用作数据。这是 Lisp 语言一个非常强大的特性。括号内带有分号和句号(即“;”和“.”)的句子不是列表。这里列举了另外一种形式的列表:
|
||||
|
||||
'(this list has (a list inside of it))
|
||||
|
||||
该表的元素包括 this,list,has 和一个子表 (a list inside of it)。这个子表由元素 a,list,inside,of,it 组成。
|
||||
|
||||
===== 原子 =====
|
||||
|
||||
在 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, string,symbol (**代表函数名的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)
|
||||
...)
|
||||
|
||||
如果第一个判断条件返回空(即为假),第一个判断语句将被跳过,然后再对第二个判断条件求值,以此类推。如果某个判断条件返回真,该判断语句将被求值。
|
||||
7
Zim/Programme/lisp/程序员的elisp入门.txt
Normal file
7
Zim/Programme/lisp/程序员的elisp入门.txt
Normal 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
|
||||
|
||||
70
Zim/Programme/lisp/程序员的elisp入门/1.txt
Normal file
70
Zim/Programme/lisp/程序员的elisp入门/1.txt
Normal 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这个东西。
|
||||
|
||||
想来想去,if,cond这种东西就不介绍了,偶相信那些在书里面花费若干
|
||||
版面介绍if,cond,loop,when,unless是什么意思,该怎么用的,都是
|
||||
骗人钱财。
|
||||
|
||||
现在准备写的,包括怎么用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求值返
|
||||
回的是t,nil求值返回的是nil。最特别的是,()求值返回nil,因为()和
|
||||
nil根本就是一回事情。
|
||||
|
||||
|
||||
一个symbol__可以同时指代变量和函数__,就是说一个foo,即是变量,又是
|
||||
函数,在lisp里面是合法的。
|
||||
|
||||
** 所有对symbol进行的操作,被称为函数。
|
||||
|
||||
使用函数的方法是(function var1 var2 var3)。这个例子很显然的告诉
|
||||
我们,括号不能出现在symbol里面,空格也不可以。但是例如*,/这样奇怪
|
||||
的符号是可以的。
|
||||
|
||||
一个函数被调用的过程,被称为**对这对括号内部求值**。注意到之前对
|
||||
symbol也叫求值,这实际上暗示var1,var2,var3不一定是一个symbol,
|
||||
也有可能是其他的函数调用。如果整个程序就是这样的函数一层一层嵌套
|
||||
得到的时候,我们可以粗略的把它称为__函数式编程语言__。
|
||||
|
||||
(function var1 var2 var3)这样的一个函数,具体求值的过程,对于
|
||||
elisp来说,它__先求function的值__,然后求每一个var的值,最后将对var求
|
||||
值得到的结果,做为参数传递给function求值的结果。之所以强调先求,
|
||||
然后求;而不是说从左到右依此求,是因为对于括号中的第一个symbol是
|
||||
特别照顾的。
|
||||
|
||||
我们至少可以总结出两点。我们对一个symbol求值时,到底是希望得到
|
||||
它代表的函数,还是希望得到它代表的变量,这__完全取决于这个symbol所__
|
||||
__处的位置__。第二,elisp中,函数和变量并不是完全平等的,你不能再象
|
||||
scheme中那样了。即使你(setq func (lambda (...))),也无法直接
|
||||
把它做为一个function来做,因为setq就注定了func只能被当作一个变量
|
||||
来处理,而lisp只会试图去找func对应的函数。
|
||||
45
Zim/Programme/lisp/程序员的elisp入门/2.txt
Normal file
45
Zim/Programme/lisp/程序员的elisp入门/2.txt
Normal 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改造的越来越象
|
||||
某一门其他的语言。
|
||||
50
Zim/Programme/lisp/程序员的elisp入门/3.txt
Normal file
50
Zim/Programme/lisp/程序员的elisp入门/3.txt
Normal 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只有__三个基本操作__:cons,car,
|
||||
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的就没有别的特殊语法了。是不是太简单了?
|
||||
Reference in New Issue
Block a user