首页 » 读书笔记 » 实用Common Lisp编程

第2章 周而复始 REPL简介

SLIME - Superior Lisp Interaction Mode for Emacs。一个用Emacs开发Lisp的环境。

REPL - Read-Eval-Print Loop,读-估值-打印的循环。

Lisp文件的后缀通常是.lisp/.cl。Lisp文件的注释符号是;(分号),注释的传统是:

;;;; 文件开头注释
;;;  大段代码开头注释
;;   小段代码注释
;    单行简单注释

在Linux下有一个名为clisp的工具编译、执行Common Lisp代码,它也是一个交互式工具。clisp可以直接执行Common Lisp代码:

clisp my.lisp

本章介绍了一堆SLIME的使用技巧,因为我用Vim,没什么帮助。Vim用户使用clisp作为交互编程环境,有2种方案可以配置为Vim mode:其一是在clisp中输入Esc Ctrl-j(M-C-j),其二是配置~/.inputrc(这个配置对所有使用readline的命令行工具都有效):

set editing-mode vi
set keymap vi

第3章 实践:简单的数据库

快速实践

构建数据的基础代码,以及格式化输出数据库:

$ cat cd.lisp 
(defvar *db* nil)
(defun add-record (cd) (push cd *db*))
(defun make-cd (title artist rating ripped)
    (list :title title :artist artist :rating rating :ripped ripped))
(add-record (make-cd "Rose" "Kathy Mattea" 7 t))
(add-record (make-cd "Fly" "Dixie Chicks" 8 t))
(defun dump-db ()
    (format t "~{~{~a: ~10t~a~%~}~%~}" *db*))

进入clisp交互式界面,加载这个文件:

[1]> (load "cd.lisp")
;; Loading file cd.lisp ...
;; Loaded file cd.lisp
T

直接输出数据库内容,以及格式化输出

[2]> *db*
((:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) (:TITLE "Rose" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T))
[3]> (dump-db)
TITLE:    Fly
ARTIST:   Dixie Chicks
RATING:   8
RIPPED:  T

TITLE:    Rose
ARTIST:   Kathy Mattea
RATING:   7
RIPPED:  T

NIL

退出clisp:

[4]> (exit)
Bye.

再次进入clisp,测试lisp编译功能:

[1]> (load (compile-file "cd.lisp"))
;; Compiling file /home/bailing/codes/learn-lisp/cd.lisp ...
;; Wrote file /home/bailing/codes/learn-lisp/cd.fas
0 errors, 0 warnings
;; Loading file /home/bailing/codes/learn-lisp/cd.fas ...
;; Loaded file /home/bailing/codes/learn-lisp/cd.fas
T

编译、加载成功,并生成了2个文件:cd.fas、cd.lib。查看,一坨看不懂的符号。跳过。

分析

定义了一个全局变量*db*(用*将全局变量括起来),将它初始化为NIL。

(defvar *db* nil)

定义了几个函数。add-record()用来向数据库添加一条记录:

(defun add-record(cd) (push cd *db))

函数参数和函数体都用()括起来。Lisp的命名风格是hello-word这样用连词符构成复合词的方法,这是标准Lisp风格。

make-cd()函数使用到属性表。在Lisp里,简单列表如:

[2]> (list 1 2 3 4)
(1 2 3 4)

另有一种属性表(property list, plist),相当于哈希表(作者誉为穷人的哈希表,Lisp有真正的哈希表)。

[3]> (list :a 1 :b 2 :c 3 :d 4)
(:A 1 :B 2 :C 3 :D 4)

从plist获取一个名字值的方法用getf()函数:

[4]> (getf (list :a 1 :b 2 :c 3 :d 4) :c)
3

了解plist,再看make-cd():生成一个plist。然后我们添加了两条记录:

(add-record (make-cd "Rose" "Kathy Mattea" 7 t)) 
(add-record (make-cd "Fly" "Dixie Chicks" 8 t)) 

注意make-cd()并没有明确的写返回值,在Lisp中,函数自动返回其最后求值的那个表达式的值,在make-cd()中则是自动返回新生成的列表(plist)。

format函数

数据库构建完毕后,直接输出*db*的值:

[2]> *db*
((:TITLE "Fly" :ARTIST "Dixie Chicks" :RATING 8 :RIPPED T) (:TITLE "Rose" :ARTIST "Kathy Mattea" :RATING 7 :RIPPED T))

这是数据的Lisp内部表示。要输出人可阅读的方式,可要费点心思。分析format()函数究竟做了些什么。

从简单输出开始。Lisp版的Hello world

[6]> (format t "hello world")
hello world
NIL

format类似printf(),t是标准输出*standard-output*的简写,最后跟一个字符串。稍微复杂一些的例子:

[6]> (format t "~a:~10t~a" :author "berlinix")
AUTHOR:   berlinix

~类似printf()的%;~a表示美化输出(:author不输出前导的:,字符串不输出引号等),并将消耗一个实参;~t表示制表,不消耗实参,~10t表示留10个空白。在格式化字符串后,跟着两个实参,与printf()一样。format还可以遍历列表,这是printf()没有的功能:

[6]> (format t "~{~a:~10t~a~}" (list :author "berlinix"))
AUTHOR:   berlinix
NIL

再复杂一点:

[6]> (format t "~{~a:~10t~a~%~}" (list :author "berlinix" :website "www.berlinix.com"))
AUTHOR:   berlinix
WEBSITE:  www.berlinix.com
NIL

分析。~{表示format要处理一个列表,~}表示列表处理完毕,~%表示输出一个换行。再次解剖以上那个format格式化字符串:

~{       ~a     :       ~10t        ~a          ~%      ~}
开始处理列表
        美化输出
                :字符
                        制表,并留10个空格
                                    美化输出
                                                换行
                                                        列表处理完成

最后,回顾数据库格式化输出那段代码:

(defun dump-db ()
    (format t "~{~{~a: ~10t~a~%~}~%~}" *db*))

*db*是一个列表(列表之列表)。因此最外层有一个~{~}。*db*中的每一行也是一个列表,因此有一个~{~},为了让每个cd记录之间有空行,因此有一个~%,此时的结构是:

~{~{~}~%~}

接下来是对每一行记录(一个列表)的处理:

~a: ~10t~a~%

每个字段有2个元素,名称和值,都分别由~a美化输出,每个字段处理完毕输出一个换行。

消除重复

首先看几个符号:'`,,@。

生成一个列表:

[17]> (list 1 2)
(1 2)
[18]> `(1 2)
(1 2)
[19]> '(1 2)
(1 2)

单引号(')和反引号(`)避免表达式被求值,他们的区别是:在反引用表达式里,任何以逗号(,)开头的表达式会被求值。

[27]> `(1 ,(+ 1 2))
(1 3)
[28]> `(1 ,(list 1 2))
(1 (1 2))

注意,(list 1 2)求值后直接以列表的形式插入到外部列表,要让表达式的值嵌入到外部列表,需要用,@代替,

[29]> `(1 ,@(list 1 2))
(1 1 2)

定义一个宏:

[33]> (defmacro backwards (expr) (reverse expr))
BACKWARDS

使用这个宏:

[35]> (backwards ("hello world!" t format))
hello world!

首先表达式("hello world!" t format)不被求值,直接作为列表传给backwards代码,backwards代码直接展开为(reverse `("hello world!" t format)),并返回(format t "hello world!"),并求值(输出hello world!)。以上代码相当于:

[50]> (reverse `("hello world!" t format))
(FORMAT T "hello world!")

在这里backwards就像定义了一个与Lisp非常类似的新语言,只是写法反了一下。Lisp的宏与C/C++的宏除了名字,其他没有相似之处,C/C++的宏只是文本替换,宏对程序结构一无所知,而Lisp的宏在本质上是由编译器成为你的代码生成器。

Lisp程序的第一印象

Lisp程序很简单,完成简单数据库这样的程序,没有像C一样的引入头文件,有一个main()函数,format比printf()也更强大。Lisp中甚少见到C系中常见的{},等标志性符号。

分享

0