目录:
- Hello,world
- A Simple Database
- 语法和语义(待补充)
- 函数(Functions)
- 变量
- 序列变量的基本操作
- 标准宏
- 自定义宏(Macors)
- 数字、字符和字符串
参考文献
3. 语法和语义(待补充)
在介绍lisp的语法和语义之前,首先了解一下它与其他语言的不同之处是非常必要的。大都数编程语言,无论是解析型还是编译型,对语法的操作都是像在黑匣子里。你将一串的表达式或语句传递给黑匣子,而程序的执行行为和编译版本都取决于它的编译器或解析器。
当然,在黑匣子中,语言处理器通常分为子系统,每个子系统负责将程序文本翻译成行为或目标代码的一部分。一个典型的划分是将处理器分成三个阶段,每一个阶段连接下一个阶段:词法分析器将字符流分解为符号,并将它们馈送到一个语法分析器中,该解析器根据程序语言的语法生成程序中表达式的树。这棵树被称之为抽象语法树,然后进入一个计算器,把它直接或将它编译成其他语言如机器代码。因为语言处理器是一个黑匣子,处理器使用的数据结构,如符号和抽象语法树,只有编译器或解析器的实现者清楚。
在lisp中事情有一些不同,程序运行结果与编译器和如何书写代码都有关系。与一个黑匣子一步决定程序行为不同,lisp定义了两个黑匣子。一个将文本转换为lisp对象称之为reader,而另一个实现程序中的这些对象的语义,称之为evaluator。
Lisp主要包含两种结构list和atom。
4. 函数(Functions)
Lisp中最基本的三个组成部分为函数(Functions)、变量(Variables)和宏(Macros)。其中Functions是所有编程语言中实现抽象的最基本的机制。实际上宏也是通过函数来实现的,只不过宏是在编译时构建。
在lisp中通过DEFUN宏来定义新的函数,最基本的定义骨架如下:
(defun name (parameter*)
"Optional documentation string."
body-form*)
函数名称可以是任何符号,例如定义++作为一个函数实现输入参数的自加运算。
? (defun ++ (a)
(+ a 1))
++
? (++ 10)
11
函数定义骨架中表示的"Optional documentation string."说明在函数声明下面第一行“”内包括的内容是函数的说明。函数的参数列表有很多种形式,lisp提供了很多复杂提供参数的方式。
4.1. 可选择参数(Optional Parameter)
在lisp函数声明的参数中,可以使用&optional符号声明在此之后的参数是选择性给出的,可选择参数的默认值为NIL,也可以为可选择参数提供默认值。在可选参数的默认值后面增加参数名+”-supplied-p”可以显示表示是否选择给出了该参数的值,例如参数c默认值为3在默认值后加c-supplied-p那么如果c取默认值则c-supplied-p为NIL反之为T,以下为示例:
(defun foo (a b &optional c d) (list a b c d))
(foo 1 2) ==> (1 2 NIL NIL)
(foo 1 2 3) ==> (1 2 3 NIL)
(foo 1 2 3 4) ==> (1 2 3 4)
声明默认值:
(defun foo (a &optional (b 10)) (list a b))
(foo 1 2) ==> (1 2)
(foo 1) ==> (1 10)
判定有默认值的参数是否赋予值:
(defun foo (a b &optional (c 3 c-supplied-p))
(list a b c c-supplied-p))
(foo 1 2) ==> (1 2 3 NIL)
(foo 1 2 3) ==> (1 2 3 T)
(foo 1 2 4) ==> (1 2 4 T)
4.1. 剩余参数(Rest Parameters)
Lisp允许在函数参数中引入&rest符号,表示在其后面的参数数量可以不限制。下面的声明是lisp中format函数与+函数的声明方式,rest符号一定在参数声明的最后:
(defun format (stream string &rest values) ...)
(defun + (&rest numbers) ...)
4.3. 关键字参数
假设我有三个输入参数,但是我只想给第一个和第三个参数赋值怎么办?换句话说指定特定的输入参数值,此时可以用关键字参数来实现,&key符号后面的参数均为关键字参数,可以使用:named的方式为特定参数赋值。
(defun foo (&key a b c) (list a b c))
(foo) ==> (NIL NIL NIL)
(foo :a 1) ==> (1 NIL NIL)
(foo :b 1) ==> (NIL 1 NIL)
(foo :c 1) ==> (NIL NIL 1)
(foo :a 1 :c 3) ==> (1 NIL 3)
(foo :a 1 :b 2 :c 3) ==> (1 2 3)
(foo :a 1 :c 3 :b 2) ==> (1 2 3)
关键字参数同样支持默认值和-supplied-p。
(defun foo (&key (a 0) (b 0 b-supplied-p) (c (+ a b)))
(list a b c b-supplied-p))
(foo :a 1) ==> (1 0 1 NIL)
(foo :b 1) ==> (0 1 1 T)
(foo :b 1 :c 4) ==> (0 1 4 T)
(foo :a 2 :b 1 :c 4) ==> (2 1 4 T)
也可以为参数强调特别的key名称,而不用:named模式。
(defun foo (&key ((:apple a)) ((:box b) 0) ((:charlie c) 0 c-supplied-p))
(list a b c c-supplied-p))
(foo :apple 10 :box 20 :charlie 30) ==> (10 20 30 T)
Lisp函数参数的几种形式可以混合,&optional和&rest配合、&rest和&key配合。
4.4. 函数返回值
RETURN-FROM宏可以用来立刻退出函数,并返回特定值(实际上RETURN-FROM不仅仅用来返回函数,也可以用来返回BLOCK)。
(defun foo (n)
(dotimes (i 10)
(dotimes (j 10)
(when (> (* i j) n)
(return-from foo (list i j))))))
4.5. 函数也是数据
在lisp中函数也是可以作为数据传递的,函数只是用DEFUN定义的对象。FUNCTION操作提供一直获得函数对象的机制,例如:
CL-USER> (defun foo (x) (* 2 x))
FOO
CL-USER> (function foo)
#<Interpreted Function FOO
’符号实际上就是FUNCTION
CL-USER> #'foo
#<Interpreted Function FOO
对于函数对象有两个操作FUNCALL和APPLY,当知道函数的具体参数数量时使用FUNCALL,例:
(foo 1 2 3) === (funcall #'foo 1 2 3)
实际上在你写函数时,有时候不知道传递进来的函数名称,但是知道参数数量,这时候才是funcall的用武之地,例:
(defun plot (fn min max step)
(loop for i from min to max by step do
(loop repeat (funcall fn i) do (format t "*"))
(format t "~%")))
CL-USER> (plot #'exp 0 4 1/2)
*
*
**
****
*******
************
********************
*********************************
******************************************************
NIL
APPLY接收任意数量的参数(统一为一个list),并且不限制&optional、&rest和&key。使用方法与FUNCALL类似。
4.6. 匿名函数
匿名函数在并不想为一段代码定义专门的函数时使用(当然,不用函数还不行),例如:
(defun double (x) (* 2 x))
(plot #'double 0 10 1)
函数double与(lambda (x) (* 2 x))功能是一样的,但是仅仅为了这个单独写一个函数可能不值当,那么就用匿名函数,这样代码更简洁,通常匿名函数都很短:
(plot #'(lambda (x) (* 2 x)) 0 10 1)
5. 变量
Lisp支持的变量有两大种类:lexical和dynamic。Lisp与其他语言例如Java或C/C++最大的区别就是lisp变量没有具体的类型,在实现上属于动态类型。
对于变量在函数中的传递,lisp每次调用函数,都会创建新的连接给函数的调用者。如果传递给函数的参数是可变的(mutable)则在函数中的修改会影响参数,否则没有影响。
LET操作用来初始化新的变量。
(let (variable*)
body-form*)
(let ((x 10) (y 20) z)
...)
LET操作定义的变量相当于重新定义局部变量有效范围仅是body-form内,不会对同名全局变量产生影响。
(defun foo (x)
(format t "Parameter: ~a~%" x) ; |<------ x is argument
(let ((x 2)) ; |
(format t "Outer LET: ~a~%" x) ; | |<---- x is 2
(let ((x 3)) ; | |
(format t "Inner LET: ~a~%" x)) ; | | |<-- x is 3
(format t "Outer LET: ~a~%" x)) ; | |
(format t "Parameter: ~a~%" x))
CL-USER> (foo 1)
Parameter: 1
Outer LET: 2
Inner LET: 3
Outer LET: 2
Parameter: 1
NIL
另外还有LET操作,基本与LET相同,尽在定义变量时LET可以引用在LET*中定义较早的变量。
(let* ((x 10)
(y (+ x 10)))
(list x y))
而LET只能这样:
(let ((x 10))
(let ((y (+ x 10)))
(list x y)))
5.1. Lexical变量和闭包(Closures)
变量的作用范围是件神奇的事情
(let ((count 0)) #'(lambda () (setf count (1+ count))))
Count之所以能传递到匿名函数,是因为匿名函数在let方法的体内,而将上面语句定义为参数,每次通过FUNCALL调用时,count就又变成了类似于全局变量的而实际上又是局部变量(因为你如果直接访问count是找不到这个变量的,只有通过函数fn才能找到),这个特性实际上可以为变量提供某种安全机制。
(defparameter *fn* (let ((count 0)) #'(lambda () (setf count (1+ count)))))
CL-USER> (funcall *fn*)
1
CL-USER> (funcall *fn*)
2
CL-USER> (funcall *fn*)
3
5.2. Dynamic,a.k.a Special,Variables
Lisp的全局函数声明有两种方式DEFVAR和DEFPARAMETER:
(defvar *count* 0
"Count of widgets made so far.")
(defparameter *gap-tolerance* 0.001
"Tolerance to be allowed in widget gaps.")
至于defvar与defparameter的不同,暂时只知道defvar可以不赋初始值。定义完的全局变量,就可以在全程序段使用了。
下面是一些对let使用的样例:
(defvar *x* 10)
(defun foo () (format t "X: ~d~%" *x*))
(defun bar ()
(foo)
(let ((*x* 20)) (foo))
(foo))
CL-USER> (bar)
X: 10
X: 20
X: 10
NIL
另外:
(defun foo ()
(format t "Before assignment~18tX: ~d~%" *x*)
(setf *x* (+ 1 *x*))
(format t "After assignment~18tX: ~d~%" *x*))
CL-USER> (bar)
Before assignment X: 11
After assignment X: 12
Before assignment X: 20
After assignment X: 21
Before assignment X: 12
After assignment X: 13
NIL
5.3. 常数
Lisp使用DEFCONSTANT定义全局常数,由于lisp在变量命名上限制非常小,可以使用-或者+等特别标记下该变量时全局常数例如:+c1+。常数不可重定义,不可修改。
5.4. 赋值
Setf是lisp中的基本赋值语句,格式如下:
(setf place value ...)
其他语言的赋值语句如下:
Assigning to ... | Java, C, C++ | Perl | Python |
---|---|---|---|
... variable | x = 10; | $x = 10; | x = 10 |
... array element | a[0] = 10; | $a[0] = 10; | a[0] = 10 |
...hash table entry | -- | $hash{'key'} = 10; | hash['key'] = 10 |
... field in object | o.field = 10; | $o->{'field'} = 10; | o.field = 10 |
在lisp中实现各种赋值样例如下,aref用来索引数组,gethash用来索引哈希表,field o相当于o.field:
名称 | 代码 |
---|---|
Simple variable: | (setf x 10) |
Array: | (setf (aref a 0) 10) |
Hash table: | (setf (gethash 'key hash) 10) |
Slot named 'field': | (setf (field o) 10) |
5.5. 其他修改数据的方法
除setf之外,还有INCF(相当于++)、DECF(相当于--)、PUSH、PUSHNEW、POP。
还有ROTATEF和SHIFTF,其中ROTATEF用来交换两个变量:
(rotatef a b)
对于普通的变量,相当于
(let ((tmp a)) (setf a b b tmp) nil)
SHIFTF命令相当于将参数列表中右侧的值付给左侧的值:
(shiftf a b 10)
相当于
(let ((tmp a)) (setf a b b 10) tmp)
6. 序列变量的基本操作
6.1. 向量(Vectors)和一维数组(Arrays)
可以使用VECTOR函数定义向量,在lisp中向量可以是任意维度的,但是生成后大小是固定的。
(vector) → #()
(vector 1) → #(1)
(vector 1 2) → #(1 2)
(vector 1 2 3 4) → #(1 2 3 4)
MAKE-ARRAY是更常用的方式用来生成数组(或者说高维向量,试想下用vector创建20维的向量,需要打20个0....)。下面的代码生成了大小为5的数组,:initial-element表示以nil初始化每一个元素。
(make-array 5 :initial-element nil) → #(NIL NIL NIL NIL NIL)
:fill-pointer用来定义某初始大小的可变长度数组(向量),下面定义了最大尺寸为5的可调整大小的向量:
(make-array 5 :fill-pointer 0) → #()
使用VECTOR-PUSH函数,向可调整大小向量中添加元素。使用VECTOR-POP函数在堆中弹出元素。
(defparameter *x* (make-array 5 :fill-pointer 0))
(vector-push 'a *x*) → 0
*x* → #(A)
(vector-push 'b *x*) → 1
*x* → #(A B)
(vector-push 'c *x*) → 2
*x* → #(A B C)
(vector-pop *x*) → C
*x* → #(A B)
(vector-pop *x*) → B
*x* → #(A)
(vector-pop *x*) → A
*x* → #()
若要使向量不限制尺寸还需要传递:adjustable参数,使用VECTOR-PUSH-EXTEND向其中添加元素。
(make-array 5 :fill-pointer 0 :adjustable t) → #()
函数LENGTH可以得到向量的大小,ELT可以通过索引得到向量中的数据。
? (defparameter *x* (vector 1 2 3))
*X*
? (length *x*)
3
? (elt *x* 0)
1
;(elt *x* 1) → 2
;(elt *x* 2) → 3
;(elt *x* 3) → error
? (setf (elt *x* 0) 10)
10
? *x*
#(10 2 3)
6.2. 序列的迭代函数(一些常规的索引方法)
针对序列lisp还提供了很多迭代类索引函数:
名称 | 需要的参数 | 返回值 |
---|---|---|
COUNT | 元素和序列 | 序列中元素出现的次数 |
(count 1 #(1 2 1 2 3 1 2 3 4)) | → | 3 |
FIND | 元素和序列 | 找到的元素或NIL |
(find 1 #(1 2 1 2 3 1 2 3 4)) | → | 1 |
(find 10 #(1 2 1 2 3 1 2 3 4)) | → | NIL |
POSITION | 元素和序列 | 元素的位置或NIL |
(position 1 #(1 2 1 2 3 1 2 3 4)) | → | 0 |
REMOVE | 元素和序列 | 删除元素后的序列 |
remove 1 #(1 2 1 2 3 1 2 3 4)) | → | #(2 2 3 2 3 4) |
(remove 1 '(1 2 1 2 3 1 2 3 4)) | → | (2 2 3 2 3 4) |
(remove #\a "foobarbaz") | → | "foobrbz" |
SUBSTITUTE | 新的元素,元素和序列 | 替换后的新序列 |
(substitute 10 1 #(1 2 1 2 3 1 2 3 4)) | → | #(10 2 10 2 3 10 2 3 4) |
(substitute 10 1 '(1 2 1 2 3 1 2 3 4)) | → | (10 2 10 2 3 10 2 3 4) |
(substitute #\x #\b "foobarbaz") | → | "fooxarxaz" |
参数列表:
参数 | 描述 |
---|---|
:test | 用来比较元素的方法(两参数)(或通过:key功能提取的值),默认为:EQL |
- | (count "foo" #("foo" "bar" "baz") :test #'string=) → 1 |
? (defun verstring= (x y)
(format t "Looking at ~s to ~s ~%" x y) (string= x y))
VERSTRING=
? (count "foo" #("foo" "bar" "baz") :test #'verstring=)
Looking at "foo" to "foo"
Looking at "foo" to "bar"
Looking at "foo" to "baz"
1
参数 | 描述 |
---|---|
:key | 从实际序列提取关键字的方法(一参数),NIL表示将元素当做is处理,默认为:NIL |
- | (find 'c #((a 10) (b 20) (c 30) (d 40)) :key #'first) → (C 30) |
- | (find 'a #((a 10) (b 20) (a 30) (b 40)) :key #'first) → (A 10) |
- | (find 'a #((a 10) (b 20) (a 30) (b 40)) :key #'first :from-end t) → (A 30) |
? (defun verbose-first (x) (format t "Looking at ~s~%" x) (first x))
VERBOSE-FIRST
? (count 'a #((a 10) (b 20) (a 30) (b 40)) :key #'verbose-first)
Looking at (A 10)
Looking at (B 20)
Looking at (A 30)
Looking at (B 40)
2
参数 | 描述 |
---|---|
:from-end | 如果为真,遍历顺序为反向,默认为NIL |
- | (find 'a #((a 10) (b 20) (a 30) (b 40)) :key #'first :from-end t) → (A 30) |
- | (remove #\a "foobarbaz" :count 1 :from-end t) →"foobarbz" |
参数 | 描述 |
- | - |
:count | 指示要删除或替换或不表示所有的元素的数目(仅移除和替换)。 |
:start | 子序列的开始索引值(包含) |
:end | 子序列的结束索引值(不包含) |
Lisp提供了一些更高级的索引方法,通过以上基本的方法添加-if或-if-not已实现满足一些复杂判断条件的索引效果,例如:
? (count-if #'evenp #(1 2 3 4 5))
2
? (count-if-not #'evenp #(1 2 3 4 5))
3
? (position-if #'digit-char-p "abcd0001")
4
? (remove-if-not #'(lambda (x) (char= (elt x 0) #\f))
#("foo" "bar" "baz" "foom"))
#("foo" "foom")
(count-if #'evenp #((1 a) (2 b) (3 c) (4 d) (5 e)) :key #'first) → 2
(count-if-not #'evenp #((1 a) (2 b) (3 c) (4 d) (5 e)) :key #'first) → 3
(remove-if-not #'alpha-char-p
#("foo" "bar" "1baz") :key #'(lambda (x) (elt x 0))) → #("foo" "bar")
比较特殊的remove还可以添加-duplicates移除重复元素:
(remove-duplicates #(1 2 1 2 3 1 2 3 4)) → #(1 2 3 4)
CONCATENATE函数将多个序列组合为一个序列:
(concatenate 'vector #(1 2 3) '(4 5 6)) → #(1 2 3 4 5 6)
(concatenate 'list #(1 2 3) '(4 5 6)) → (1 2 3 4 5 6)
(concatenate 'string "abc" '(#\d #\e #\f)) → "abcdef"
6.3. 排序与合并(Sort和Merging)
行数SORT与STABLE-SORT提供了两种排序序列的方式,这两种函数都需要两个参数并且返回一个排序好的序列:
(sort (vector "foo" "bar" "baz") #'string<) → #("bar" "baz" "foo")
Merg函数将两个序列有序的合并为一个:
(merge 'vector #(1 3 5) #(2 4 6) #'<) → #(1 2 3 4 5 6)
(merge 'list #(1 3 5) #(2 4 6) #'<) → (1 2 3 4 5 6)
6.4. 子序列操作
subseq返回一个序列的子序列:
(subseq "foobarbaz" 3) → "barbaz"
(subseq "foobarbaz" 3 6) → "bar"
Subseq的子序列的修改是可以对原始序列产生影响的:
(defparameter *x* (copy-seq "foobarbaz"))
(setf (subseq *x* 3 6) "xxx") ;子序列和替换序列长度相同
*x* → "fooxxxbaz"
(setf (subseq *x* 3 6) "abcd") ; 替换序列比子序列长,忽略多余
*x* → "fooabcbaz"
(setf (subseq *x* 3 6) "xx") ; 替换序列比子序列短,仅替换两个
*x* → "fooxxcbaz"
可以用FILL函数将序列中的多个元素替换为某一个值,:start和:end参数可以用来限制子序列的行为。
Search函数与POSITION函数类似,但可以在序列中搜索子序列:
(position #\b "foobarbaz") → 3
(search "bar" "foobarbaz") → 3
MISMATCH函数用来找到两个序列第一次出现不匹配的位置,若完全匹配则返回NIL。当然也可以使用from-end来从末尾开始检索。
(mismatch "foobarbaz" "foom") → 3
(mismatch "foobar" "bar" :from-end t) → 3
6.5. 序列的整体判定
EVERY、SOME、NOTANY、NOTEVERY:
(every #'evenp #(1 2 3 4 5)) → NIL
(some #'evenp #(1 2 3 4 5)) → T
(notany #'evenp #(1 2 3 4 5)) → NIL
(notevery #'evenp #(1 2 3 4 5)) → T
(every #'> #(1 2 3 4) #(5 4 3 2)) → NIL
(some #'> #(1 2 3 4) #(5 4 3 2)) → T
(notany #'> #(1 2 3 4) #(5 4 3 2)) → NIL
(notevery #'> #(1 2 3 4) #(5 4 3 2)) → T
6.6. MAPPING函数
要求两个向量每一个元素的乘积得到的新向量:
(1 2 3 4 5).*(10 9 8 7 6)=(10 18 24 28 30)
可以利用MAP函数:
(map 'vector #'* #(1 2 3 4 5) #(10 9 8 7 6)) → #(10 18 24 28 30)
将a、b、c的和保存到a中可以用MAP-INTO:
(map-into a #'+ a b c)
REDUCE函数仅对一个序列进行操作,下面的代码计算出序列的和:
reduce #'+ #(1 2 3 4 5 6 7 8 9 10)) → 55
下面的代码求得一个序列的最大值:
reduce #'+ #(1 2 3 4 5 6 7 8 9 10)) → 10
REDUCE支持:key、:from-end、:start和:end关键参数。
6.7. 哈希列表
通常使用make-hash-table创建一个空的哈希表,gethash用来获取hash表中的值,没有当前关键字对应的值则返回NIL,gethash的结果是可以直接修改的,gethash返回两个值,第一个为value第二个是布尔变量表示是否有当前键值。
(defparameter *h* (make-hash-table))
(gethash 'foo *h*) → NIL
(setf (gethash 'foo *h*) 'quux)
(gethash 'foo *h*) → QUUX
MULTIPLE-VALUE-BIND创建变量的绑定,用法与LET类似。下面的函数将value与gethash返回的键值和present与gethash返回的是否存在绑定。
(defun show-value (key hash-table)
(multiple-value-bind (value present) (gethash key hash-table)
(if present
(format nil "Value ~a actually present." value)
(format nil "Value ~a because key not found." value))))
(setf (gethash 'bar *h*) nil) ; provide an explicit value of NIL
(show-value 'foo *h*) → "Value QUUX actually present."
(show-value 'bar *h*) → "Value NIL actually present."
(show-value 'baz *h*) → "Value NIL because key not found."
可以用maphash函数来遍历哈希列表,例如下面的代码遍历并打印了整个哈希列表:
(maphash #'(lambda (k v) (format t "~a => ~a~%" k v)) *h*)
REMHASH可以用来删除哈希表中的值:
(maphash #'(lambda (k v) (when (< v 10) (remhash k *h*))) *h*)
遍历哈希表也可以通过loop(loop的具体介绍后面在所,下面来看一下这个跟白话一样的代码见识一下):
(loop for k being the hash-keys in *h* using (hash-value v)
do (format t "~a => ~a~%" k v))
网友评论