**lisp** 學習筆記

開發環境

開發環境:

SBCL
Emacs
Slime:在emacs里面幫助進行common lisp開發的擴展
quicklisp:Common Lisp的包管理工具

安裝步驟:

安裝emacs

這個不用多講了。從GNU的網站能下載到不同平臺的版本。

安裝SBCL

brew install sbcl       (不支持左右箭頭移動光標. 安裝rlwrap支持)
brew install rlwrap   (命令行輸入交互增強工具, 支持方向鍵和歷史命令)
rlwrap sbcl     (啟動sbcl命令)

sbcl被安裝到/usr/local/bin目錄中(記住此地址,后面配置有用)

安裝quicklisp

$ curl -O http://beta.quicklisp.org/quicklisp.lisp
$ sbcl --load quicklisp.lisp
(quicklisp-quickstart:install)
(ql:add-to-init-file)
(ql:quickload "quicklisp-slime-helper")

安裝教程:http://www.quicklisp.org/beta/#installation
查找可用的包的信息,可以到cliki上去搜。

安裝slime

在sbcl里面,運行:

(ql:quickload "quicklisp-slime-helper")  

根據提示修改Emacs的配置文件。

cd ~/ && vi .emacs
添加下面兩句
(load (expand-file-name "~/quicklisp/slime-helper.el"))
(setq inferior-lisp-program “/usr/local/bin/sbcl”)  ;你的sbcl路徑  

或者下載slime,地址 http://common-lisp.net/project/slime/#downloading
把slime文件夾copy到了~/.emacs.d/目錄中
配置.emacs配置文件:

(setq inferior-lisp-program "/usr/local/bin/sbcl")  
(add-to-list 'load-path "~/.emacs.d/slime/")  
(require 'slime)  
(slime-setup)  

做完上面的步驟以后,打開emacs,按M-x,輸入slime
這篇文章也有助于了解Slime: http://www.open-open.com/lib/view/open1400054028504.html

Lisp 基礎語法

表達式:

表達式或是一個原子,或是一個由零個或多個表達式組成的表(list)。
表達式之間用空格分開,放入一對括號中。
在算術中,表達式 1 + 1 得出值2。
Lisp表達式也有值,如果表達式e得出 值v,我們說e返回v。
如果一個表達式是表,我們稱第一個元素為操作符,其余的元素為自變量。
七個原始操作符:quote,atom,eq,car,cdr,cons,cond

通過引 用(quote)一個表,我們避免它被求值. 一個未被引用的表作為自變量傳給象
atom這樣的操作符將被視為代碼:

(atom (atom 'a))
=> T

反之一個被引用的表僅被視為表, 在此例中就是有兩個元素的表:

(atom '(atom 'a))
=> NIL

表與列表操作

Lisp的全名叫“表處理語言”,LISt Procesor 。簡單說來,用小括號括起來的表達式式就叫表。而表里面的東西,就是原子,表里不僅可以包含原子,也可以包含另一個表。也就是說表可以嵌套。最小的表就是空表 ( )。
在lisp中程序和數據都是用 表 來表示。
在lisp中 T 表示邏輯真;NIL表示邏輯假,同時也是空表。
Lisp會對所有的表求值,如果想使用表本身 (作為數據), 需在表前加 ‘ 操作符 (單引號)。

'(+ 1 2)            
=> (+ 1 2)

這次解譯器不對這個表其求值了,而是原來這個表本身。
在所有的表中,第一個原子總是函數,代表操作、指令、命令。而之后原子(或表)是參數,意即對操作的說明

操作表

car: 取出表的第一個元素并返回該元素(同first)
last: 返回最后一個元素的表
cdr: 返回除第一個元素之外的表(同rest)

(car '((1 2) 3))    =>(1 2)
(cdr '(+ 1 2 3))        =>  (1 2 3)     #取出函數的參數
(cdr '(1))              =>  NIL
(cadr '(1 2 3))         =>  2           #等價(car (cdr (cdr '(1 2 3))))

其它:cadr or caddr

構造表

cons 函數:
連接一個元素與一個表,接受兩個參數,參數順序不可顛倒。第二個參數為列表時,才能返回一個列表。cons 的作用是將兩棵樹連接成一棵樹。

(cons 1 '(2 3))                 => (1 2 3) 
(cons 2 3)                      => (2 . 3)  
(cons 1 (cons 2 '(3)))          => (1 2 3)   連接三個或以上的元素
(cons 1 (cons 2 (cons 3 nil)))  => (1 2 3)
(cons 3 nil)                    => (3)

為什么(cons 2 3) 返回 (2 . 3)呢?中間有一個 “.”呢?

這是因為表實際上是一個樹(二叉樹),在S表達式中, 二叉樹表示為 (Left . Right)。
如果左支是表,成為形式:((List) . Right)
如果右支是表,表示為 (Left . (List)) ,此時點可以省略,寫成(Left List)
'(3 . (2 3)) => (3 2 3)
這就是為什么cdr操作符會取出除第一個外的所有元素,因為它實質是取二叉樹的右支。

append 函數:
它會把最外一層括號去掉,然后連接

(append '(3 3) '(4 4))          =>  (3 3 4 4)  連接兩個表
(append '((3)) '(4 4))          =>  ((3) 4 4)

list 函數:
list 函數將所有的參數放入一個表中并返回

(list 1 1 1 1)                  =>  (1 1 1 1)  返回包含所有參數的表
(list '(2 3) '(2) 1 2)          =>  ((2 3) (2) 1 2)

構造函數 cons帶有兩個參數:一個原子和一個列表。cons 將該原子作為第一個元素添加到該列表。如果對 nil 調用 cons,Lisp 將 nil 作為空列表對待,并構建一個含一個元素的列表。append 連接兩個列表。list 包含一個由所有參數組成的列表

原子和值

原子
可以是任何數或者字母排列。空表就是原子NIL。

'sdf  => SDF    原子前面加一個引用符(單引號),返回這個原子本身

atom 運算符
判斷一個元素是不是原子

(atom 'a)       => T        a 是一個原子
(atom '(3))     => NIL  (3) 是一個列表而不是原子

setq 運算符
綁定一個變量

(setq a 5)      => 5
a  => 5

setq的意義就是賦值并且將此值返回。就是說表達式(setq a 5)的值是5
我們可以接著

(setq a 6)      => 6
(cons a '(3))       => (6 3)
(setq a 'b)     => B
(cons a '(3))       => (B 3)
(setq a '(1 2 3))   => (1 2 3)
(cdr a)         => (2 3)

函數

函數定義

(defun 函數名 (參數列表)
  (first (rest lst));執行體
)

defun 用來定義函數。第一個參數是函數名,第二個參數是參數列表,第三個參數是希望執行的代碼
Lisp 所有代碼都表述為列表。可以像操縱其他任何數據一樣操縱應用程序。

斷言函數

atom 函數
用來判斷一個表達式是不是原子

(atom (+ 1 1))      => T
(atom '(3))     => NIL

因為2是原子,而(3)是個表。

null 函數
NULL函數用來判斷表達式的值是不是NIL。

(null nil)          => T
(null (car '(3)))       => NIL

equal 函數
用來判斷兩個表達式的值是否完全相等

(equal 's 's)       => T
(equal '(s) '(s))       => T

高階函數

在 Lisp 中,由于函數和列表沒有任何區別,高階函數也就非常簡單。
高階函數的最常見用法或許是 lambda 表達式,這是閉包的 Lisp 版。lambda 函數是用于將高階函數傳入 Lisp 函數的函數定義。
例如,下列lambda 表達式計算了兩個整數的和:
(setf total '(lambda (a b) (+ a b)))
(LAMBDA (A B) (+ A B))
total
(LAMBDA (A B) (+ A B))
(apply total '(101 102))
203

如果使用過高階函數或閉包,那么可能更容易理解清單 10 中的代碼。第一行代碼定義了一個 lambda

表達式并將其和 total 符號綁定到一起。第二行代碼僅顯示了這個和 total 綁定到一起的 lambda 表達式。最終,最后一個表達式對包含 (101 102) 的列表應用這個 lambda 表達式。
高階函數提供比面向對象概念更高層次的抽象??梢杂盟鼈儊砀啙嵡逦乇磉_想法。編程的至高境界就是在不犧牲可讀性或性能的前提下,用更少的代碼提供更強大更靈活的功能。高階函數能實現所有這些要求。

Lisp 還有兩種類型的高階函數。其中功能最強大的可能是宏。宏為后面的執行定義 Lisp 對象??梢詫⒑昕醋鞔a模板。請參考清單 11 中的示例:
清單 11. 宏

(defmacro times_two (x) (* 2 x))
TIMES_TWO
 
(setf a 4)
4
 
(times_two a)
8

這個示例應該分為兩個階段進行閱讀。第一次賦值定義了宏 times_two。在第二個階段(稱為宏擴展)中,在對 a 求值之前,將 a 擴展為 (* 2 a)。該模板中這項延遲求值方式使宏的功能非常強大。Lisp 語言本身的許多功能都是基于宏的。

條件結構

Cond 函數:

(cond 分支列表1 分支列表2 分支列表3 ... )

分支列表的構成: (條件p 值e)
Cond 將對每一個“條件p”求值,如果為NIL,就接著求下一個,如果為真,就返回相應的“值e”,如果沒有一個真值,cond操作符返回nil。

if 函數:

(if 判斷表達式 真值時的返回值 假值時的返回值)

eg:計算兩個整數中的最大值

(defun my_max (x y)
  (if (> x y) x y)
)

遞歸

遞歸計算列表的總和:

(defun total (x)
  (if (null x)
    0
    (+ (first x) (total (rest x)))
  )
)
(total '(1 5 1))   =>  7

total 函數將列表當作單個的參數。第一個 if 語句在列表為空的情況下中斷遞歸,返回零值。否則,該函數將第一個元素添加到列表其余部分的總和?,F在應該明白如此構建 first 和 rest 的原因。first 能夠去除列表的第一個元素,rest 簡化了將尾部遞歸應用于列表其余部分的過程。

遞歸計算列表的長度:

(defun len (x) (cond ((null x) 0) (t (+ (len (cdr x)) 1))))
(len '(a b c d)) => 4

len用來計算一個表x的長(即元素個數)度
遞歸式是(len (cdr x)) ,終結條件是(null x)為真

trace函數
用來跟蹤函數調用的情況

(trace len)
(len '(a b c d)) 
=>
0: (LEN (A B C D))
    1: (LEN (B C D))
      2: (LEN (C D))
        3: (LEN (D))
          4: (LEN NIL)
          4: LEN returned 0
        3: LEN returned 1
      2: LEN returned 2
    1: LEN returned 3
0: LEN returned 4

基本操作符

7個基本操作符對應7大公理,任何其他函數都可以由其定義。也就是說,7個基本操作符包含了Lisp的所有語義。
這7個基本操作符是:

  1. Quote
  2. Atom
  3. Eq
  4. Car
  5. Cdr
  6. Cons
  7. Cond

下面的函數系統Lisp都有提供,我們也可以用7個操作符函數重新實現一遍。

NULL函數
NULL函數用于檢測表是否為空,或者元素是否為nil。

(defun null2 (x) (cond ((equal x nil) t) (t nil))))

解釋:如果參數與nil相等,就返回t,否則返回nil。這和邏輯學上的not函數是一致的(但null函數的應用范圍更廣,因為它可以應用于表)。

And函數

(defun and2 (x y) (cond ((equal x nil) nil) ((not (equal y nil)) t) (t nil)))

Or函數

(defun or2 (x y) (cond ((equal x t) t) ((equal y t) t)))

Last 函數

(defun last2 (x) (cond ((equal (cdr x) nil) x) (t (last2 (cdr x)))))

Length函數
下面講如何計算一個表x的長(即元素個數)度。

(defun len (x) (cond ((null x) 0) (t (+ (len (cdr x)) 1))))

遞歸式是(len (cdr x)) ,終結條件是(null x)為真。

Append函數
設參數形式是x和y。很容易分析出來,遞歸式是(cons (car x) (append2 (cdr x) y)),終結條件是當x為NIL時,返回y。

(defun append2 (x y) (cond ((eq x nil) y) (t (cons (car x) (append2 (cdr x) y)))))

Equal函數
設參數形式是x和y。很容易分析出來,遞歸式是(equal (cdr x) (cdr y)),遞歸條件是(equal (car x) (car y)),終止條件是(equal (cdr x) nil)或者(equal (cdr y) nil)或者((atom x) (equal x y))

(defun equal2 (x y) 
  (cond 
    ((null x) (not y))
    ((null y) (not x))
    ((atom x) (eq x y))
    ((atom y) (eq x y))
    ((not (equal2  (car x) (car y))) nil)
    (t (equal2 (cdr x) (cdr y)))
  )
)

代碼解釋:

((null x) (not y))
首先,如果x為空,說明遇到了x列表的末尾,這時檢測y列表是否也到了,如果到了(此時我們知道之前的元素都相等),那么返回真,否則返回假。

((null y) (not x))

如果y到了末尾,一樣處理。
((atom x) (eq x y))
如果x是一個原子,說明函數是從(equal2 (car x) (car y))字句進入的,且(car x)的結果為原子。這時函數就可以結束了,返回x=y的結果。
((atom y) (eq x y))
如果y是一個原子,說明函數是從(equal2 (car x) (car y))字句進入的,且(car y)的結果為原子。這時函數就可以結束了,返回x=y的結果。
(t (equal2 (cdr x) (cdr y)))
否則的情況,我們就遞歸。
總結,大家可以發現,其實這個函數的遞歸路徑有兩個。

If函數
用cond可以實現if函數。實際上,在類c語言中,if語句強調的是程序的走向,但在Lisp中,程序的走向可以忽略(從某種意義上),而強調的是返回值。

 (defun if2 (p e1 e2)
 (cond (p e1) (t e2))
 )

其他

#'      組合符號  等價函數 function

Lisp開發Web程序

用lisp可以開發出一套“子語言”,用于生成html。現成的成熟框架,可以參考cl-who。

可以把lisp代碼編譯成javascript。你用lisp編寫的程序,到了html里,就成了javascript??梢钥匆幌聀arenscript(http://www.cliki.net/parenscript)

cliki上搜一下web framework,你可以得到一大堆。你可以試一下cl-weblocks。閱讀一下它的文檔,你可以看到很多厲害的特性,忍不住會上手試一下!http://weblocks-framework.info 上面有不錯的文檔。有的文檔資料可能舊了點,但能夠說清楚。

使用Weblocks編寫Web應用

安裝weblocks

(ql:quickload :weblocks)

安裝demo程序

(ql:quickload :weblocks-demo)

運行

(weblocks:start-weblocks)    ;可以在8080端口啟動一個http服務。

運行示例

(weblocks-demo:start-weblocks-demo :port 3455)

訪問:http://localhost:3455/weblocks-demo
(如果發現這樣的報警,可以忽略,不影響:An instance of WEBLOCKS-DEMO with name weblocks-demo is already running, ignoring start request)

創建自己的應用

(wop:make-app 'NAME "DIR")    ;建立名稱為Name的應用,放在DIR目錄下

例如:(wop:make-app 'firstapp "home/richard/lisp")
這樣就會在“home/richard/lisp”目錄建立一個firstapp子目錄,里面有幾個必要的目錄和模板程序。

加載和修改自己的應用
要能在開發環境加載和修改自己的應用,需要能夠找到firstapp。這需要了解一下asdf的配置。asdf是lisp的模塊依賴的管理工具。具體可以參考:

http://www.common-lisp.net/project/asdf/
http://basiccoder.com/constructing-common-lisp-package-by-asdf.html
http://blog.csdn.net/xiaojianpitt/article/details/7727152

我的配置:
建立目錄:/home/richard/.config/common-lisp/source-registry.conf.d
在里面建兩個配置文件:
01practicals.conf
內容: (:tree "/home/richard/practicals-1.0.3/")
這樣就可以隨時加載執行《實用commonlisp編程》里面的例子了。
比如:(ql:quickload :chapter31)就可以加載第31章的例子。
02richard.conf 指向我自己的工作目錄。
內容:(:tree "/home/richard/lisp/")。
這樣就可以找到firstapp。用(ql:quickload :firstapp)來加載和修改。

加載后,運行

(in-package :firstapp)  切換到:firstapp包;
(start-firstapp :port 3456)  啟動應用,在3456端口監聽

學習資料推薦
Lisp的本質
http://www.cnblogs.com/Leap-abead/articles/762180.html
Lisp之根源(The roots of Lisp)
中文版:http://daiyuwen.freeshell.org/gb/rol/roots_of_lisp.html
Lisp之美
https://www.ibm.com/developerworks/cn/java/j-cb02067.html
Common Lisp 初學者快速入門指導
https://my.oschina.net/freeblues/blog/131557#1.2
在Mac下搭建Common Lisp開發環境(Emacs)
http://it.taocms.org/06/954.htm

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容