Lisp 入門/第二章 表、CAR、CDR
第二章 表、CAR、CDR
編輯表和原子
編輯Lisp的全名叫「表處理語言」,LISt Procesor 。可見表在Lisp中的重要性。
簡單說來,用小括號括起來的表達式式就叫表。
比如:
(+ 1 2)
就是一個表。
而表裡面的東西,就是原子。比如上面的這個表里,+ 是原子,1 是原子,2 也是原子。
原子就是不包含空格的符號,可以是字符,也可以是數字。比如,下面的這個表里,有兩個原子。
'(hello world)
這兩個原子分別是 hello 和 world。
其實,上面的那個式子,也是 LISP 語言中的 hello world 程序。很簡單,對吧。不過別忘記括號前面的單引號,否則會報錯。
表里不僅可以包含原子,也可以包含另一個表。舉個例子:
(or (> 3 4) (> 4 2))
在上表中,or是原子,而 (> 3 4) 、(> 4 2) 都是表,不是原子。當然了,這兩個小表里,包含的東西都是元素。
也就是說:表是可以嵌套的。
表的大小並沒有限制,最小的表就是空表:
()
程序和數據
編輯編程這項工作,是在寫一個程序,而寫程序的目的是為了處理數據。程序與數據是編程中的兩大要素,而在 Lisp 中,這兩者都用 表 來表示。
如果你在解譯器中輸入一個表,那麼Lisp會對這個表求值。所以在解釋器中輸入
(+ 1 2)
會列印3。
說明這個表的值是3。
我們再看一個例子:
(1 2 3)
如果你在解釋器中輸入上面的表達式,會得到一個錯誤:Error: 1 is invalid as a function. 意思是1不是一個可用函數。
在所有的表中,第一個原子總是函數,代表操作、指令、命令。而之後原子(或表)是參數,意即對操作的說明。所以 (+ 1 2) 表示 1 + 2,而 (- 4 3) 表示 4 -3。
例外是這個
()
這是一個空表,會返回
NIL
NIL是一個原子,表示邏輯假,但它同時也是空表。它是LISP語言中唯一一個既是表又是原子的東西。
程序是表,那數據該如何表示? 數據也是用表來表示。
Lisp會對所有的表求值,但如果我們想使用表本身(作為數據),這反而會成為一件麻煩事。只要用一個簡單的操作符就可以防止求值,那就是 ' 操作符(單引號)。
'(+ 1 2)
這次解譯器不對這個表其求值了,結果不再是3這個原子,而是一個表,原封未動的表。
(+ 1 2)
還記得我們的 hello world 程序嗎?
'(Hello world!)
會返回
(HELLO WORLD!)
各種程序語言的入門書籍都喜歡一個hello, world程序。在此鄭重告訴大家, Lisp 也有自己的 Hello,world!
上面我們輸入的是小寫的字母,輸出的卻是大寫的字母。這是因為在 Lisp 中大小寫無所謂。
不過 ' 這個東西其實只是一個語法糖(為了讓程式設計師少打幾個字符而創造的語法)。它其實是一種簡寫,全稱是quote操作,意思是引用。上面的hello world 程序如果寫全了,就是:
(quote (Hello world!))
CAR 操作符
編輯CAR操作符的作用是取出表的第一個元素。
(car '(1 2 3 4 5))
上表會返回 1。
CAR操作符的作用是取出表的第一個元素,注意,我說的是元素不是原子,所以car的返回值也可能是個表。
讓我們來試試
(car '((1 2) 3))
返回的值就是一個表。CAR 的作用就是取出第一個元素,至於第一個元素是表還是原子,它並不關心。
不過,CAR 這個名稱真的是很古老了,我們可以用 first 操作符來替代它。實際上,first 是 CAR 的別名。
注意到,我們上面的代碼在參數表是由一個單引號(引用操作)引導的。這是非常重要的。如果我們去掉單引號。
(car (1 2 3 4 5))
系統將會報錯: error: 1不是可用的函數
如果大家還記得我們之前的報錯,就會知道,這是因為系統試圖執行內表中的代碼。是的,在 LISP 中,表就是程序。 如果沒有特殊說明系統會對一切看得到的表求值。所以,我們要加上單引號,表示想要以數據的形式使用這個表。
還有一個事情是值得注意的,CAR 操作只對表管用,不可以把它用在原子上。比如:
(car 1)
系統會返回錯誤: error: 1不是「表」類型
CDR 操作符
編輯CDR 操作符和 CAR 的作用是相反的,它的作用是去除表的第一個元素。
(cdr '(1 2 3 4 5))
上面表達式的返回值是
(2 3 4 5)
CDR 總是返回一個表。它的意義,就是取出表中除了第一個元素之外的所有元素,然後返回這些元素組成的表。
CDR 的別名是 rest,這也是非常形象的名字,意思是其餘,即除了第一個以外的其餘。
我們可以用CDR操作符取出函數的參數,比如
(cdr '(+ 1 2 3))
這行代碼可以取出(+ 1 2 3)的參數(1 2 3)。
如果表中只有一個元素,那麼對這個表進行 CDR 操作,將會發生什麼事情呢?讓我們來試驗一下:
(cdr '(1))
將會返回 NIL。不要忘記,NIL 也代表一個空表。所以,上面的表達式返回的其實是一個空表。
通過了解 CAR 和 CDR操作符,你會有種感覺,第一個元素竟然和其他所有元素平等。是的,這確實是 LISP 的世界觀,至於為什麼,你讀下去就會知道。
CXR 組合操作符
編輯問你一個問題,如何取出第二個元素?
先不要急著往下看,好好想想。
我們至今只接觸了兩個運算符,一個是CAR,可以取出第一個,另一個是CDR,可以取出其餘的。那麼如何用這兩個運算符取出第二個元素呢?
答案在下面:
(car (cdr '(1 2 3)))
第二個元素就是去掉第一個元素之後的第一個元素。我們先用CDR取出除第一個元素外的其他元素(本質上就是去掉了第一個元素),然後就可以在這個新表中取出第一個元素了,這樣,我們得到的就是原表的第二個元素。
哇,聰明如你,是不是悟到了一種方法,可以取任何第幾個元素。
那思考一下如何取第三個元素吧,寫出來,在解譯器上試驗一下吧
恭喜你,你的第一個原創Lisp代碼實現了。如果還沒實現,那麼恭喜你,下面是代碼:
(car (cdr (cdr '(1 2 3))))
實際上,LISP 中由專門的操作符來表示這些操作,比如 CADR
試試下面的代碼:
(cadr '(1 2 3))
你也可以自己尋找更多類似的操作符。