形式(Form)
人可以透過實踐來學習一件事,這對於 Lisp 來說特別有效,因為 Lisp 是一門互動式的語言。任何 Lisp 系統都含有一個互動式的前端,叫做頂層(toplevel)。你在頂層輸入 Lisp 表示式,而系統會顯示它們的值。
許多 Common Lisp 的實現用 >
作為頂層提示符。
$ clisp
> 1
1
>
列印的值與輸入的值相同,數字 1 稱之為對自身求值。
> (+ 2 3)
5
在表示式 (+ 2 3) 裡, +
稱為運算子,而數字 2
跟 3
稱為實參。
在 Lisp 裡,把 +
運算子寫在前面,接著寫實參,再把整個表示式用一對括號包起來。
這稱為 「前序表示式」。
日常生活的表示法,要寫兩次 +
號 2 + 3 + 4
,在 Lisp 裡,只需要增加一個實參:(+ 2 3 4)
。
日常生活中用 +
時,它必須有兩個實參,一個在左,一個在右。前序表示法的靈活性代表著,在 Lisp 裡, +
可以接受任意數量的實參,包含了沒有實參:
> (+)
0
> (+ 2)
2
> (+ 2 3)
5
> (+ 2 3 4)
9
> (+ 2 3 4 5)
14
由於運算子可接受不定數量的實參,我們需要用括號來標明表示式的開始與結束。
表示式可以巢狀。即表示式裡的實參,可以是另一個複雜的表示式:
> (/ (- 7 1) (- 4 2))
3
所有的 Lisp 表示式,要麼是 1 這樣的數原子,要麼是包在括號裡,由零個或多個表示式所構成的列表。以下是合法的 Lisp 表示式:
2 (+ 2 3) (+ 2 3 4) (/ (- 7 1) (- 4 2))
所有的 Lisp 程式都採用這種形式。而像是 C 這種語言,有著更復雜的語法:算術表示式採用中序表示法;函式呼叫採用某種前序表示法,實參用逗號隔開;表示式用分號隔開;而一段程式用大括號隔開。
求值(Evaluation)
在 Lisp 裡, +
是函式,然而如 (+ 2 3)
的表示式,是函式呼叫。
當 Lisp 對函式呼叫求值時,它做下列兩個步驟:
- 首先從左至右對實參求值。在這個例子當中,實參對自身求值,所以實參的值分別是
2
跟3
。 - 實參的值傳入以運算子命名的函式。在這個例子當中,將
2
跟3
傳給+
函式,返回5
。
如果實參本身是函式呼叫的話,上述規則同樣適用。以下是當 (/ (- 7 1) (- 4 2))
表示式被求值時的情形:
- Lisp 對
(- 7 1)
求值:7
求值為7
,1
求值為1
,它們被傳給函式-
,返回6
。 - Lisp 對
(- 4 2)
求值:4
求值為4
,2
求值為2
,它們被傳給函式-
,返回2
。 - 數值
6
與2
被傳入函式/
,返回3
。
但不是所有的 Common Lisp 運算子都是函式,不過大部分是。函式呼叫都是這麼求值。由左至右對實參求值,將它們的數值傳入函式,來返回整個表示式的值。這稱為 Common Lisp 的求值規則
。
如果輸入 Lisp 不能理解的東西,它會列印一個錯誤訊息,接著帶你到一種叫做中斷迴圈(break loop)的頂層。輸入 :abort
跳出:
> (/ 1 0)
Error: Division by zero
Options: :abort, :backtrace
>> :abort
>
一個不遵守 Common Lisp 求值規則的運算子是 quote
。 quote
是一個特殊的運算子,意味著它自己有一套特別的求值規則。這個規則就是:什麼也不做。quote
運算子接受一個實參,並完封不動地返回它。
> (quote (+ 3 5))
(+ 3 5)
使用縮寫 '
比使用整個 quote
表示式更常見。
Lisp 提供 quote
作為一種保護表示式不被求值的方式。
資料(Data)
Lisp 提供了所有在其他語言找的到的,以及其他語言所找不到的資料型別。一個我們已經使用過的型別是整數(integer),整數用一系列的數字來表示,比如: 256
。另一個 Common Lisp 與多數語言有關,並很常見的資料型別是字串(string),字串用一系列被雙引號包住的字串表示,比如: "ora et labora"
。整數與字串一樣,都是對自身求值的。
有兩個通常在別的語言所找不到的 Lisp 資料型別是符號(symbol)與列表(lists),符號是英語的單詞 (words)。無論你怎麼輸入,通常會被轉換為大寫:
> 'Artichoke
ARTICHOKE
列表是由被括號包住的零個或多個元素來表示。元素可以是任何型別,包含列表本身。使用列表必須要引用,不然 Lisp 會以為這是個函式呼叫:
> '(my 3 "Sons")
(MY 3 "Sons")
> '(the list (a b c) has 3 elements)
(THE LIST (A B C) HAS 3 ELEMENTS)
注意引號保護了整個表示式(包含內部的子表示式)被求值。
你可以呼叫 list
來建立列表。由於 list
是函式,所以它的實參會被求值。這裡我們看一個在函式 list
呼叫裡面,呼叫 +
函式的例子:
> (list 'my (+ 2 1) "Sons")
(MY 3 "Sons")
為什麼我們需要 quote
,如果一個列表被引用了,則求值規則對列表自身來求值;如果沒有被引用,則列表被視為是程式碼,依求值規則對列表求值後,返回它的值。
> (list '(+ 2 1) (+ 2 1))
((+ 2 1) 3)
在 Common Lisp 裡有兩種方法來表示空列表:
> ()
NIL
> nil
NIL
列表操作(List Operations)
用函式 cons
來構造列表。如果傳入的第二個實參是列表,則返回由兩個實參所構成的新列表,新列表為第一個實參加上第二個實參:
> (cons 'a '(b c d))
(A B C D)
可以透過把新元素建立在空表之上,來構造一個新列表。list
,不過就是一個把幾個元素加到 nil
上的快捷方式:
> (cons 'a (cons 'b nil))
(A B)
> (list 'a 'b)
(A B)
取出列表元素的基本函式是 car
和 cdr
。對列表取 car
返回第一個元素,而對列表取 cdr
返回第一個元素之後的所有元素:
> (car '(a b c))
A
> (cdr '(a b c))
(B C)
> (car (cdr (cdr '(a b c d))))
C
> (third '(a b c d))
C
真假(Truth)
在 Common Lisp 裡,符號 t
是表示邏輯 真
的預設值。與 nil
相同, t
也是對自身求值的。如果實參是一個列表,則函式 listp
返回 真
:
> (listp '(a b c))
T
函式的返回值將會被解釋成邏輯 真
或邏輯 假
時,則稱此函式為謂詞(predicate)。在 Common Lisp 裡,謂詞的名字通常以 p
結尾。
邏輯 假
在 Common Lisp 裡,用 nil
,即空表來表示。如果我們傳給 listp
的實參不是列表,則返回 nil
。
> (listp 27)
NIL
由於 nil
在 Common Lisp 裡扮演兩個角色,如果實參是一個空表,則函式 null
返回 真
。
> (null nil)
T
而如果實參是邏輯 假
,則函式 not
返回 真 :
> (not nil)
T
null
與 not
做的是一樣的事情。
在 Common Lisp 裡,最簡單的條件式是 if
。通常接受三個實參:一個 test 表示式,一個 then 表示式和一個 else 表示式。若 test
表示式求值為邏輯 真
,則對 then
表示式求值,並返回這個值。若 test
表示式求值為邏輯 假
,則對 else
表示式求值,並返回這個值:
> (if (listp '(a b c))
(+ 1 2)
(+ 5 6))
3
> (if (listp 27)
(+ 1 2)
(+ 5 6))
11
與 quote
相同,if
是特殊的運算子。不能用函式來實現,因為實參在函式呼叫時永遠會被求值,而 if
的特點是,只有最後兩個實參的其中一個會被求值。if
的最後一個實參是選擇性的。如果忽略它的話,預設值是 nil
:
> (if (listp 27)
(+ 1 2))
NIL
雖然 t
是邏輯 真
的預設表示法,任何非 nil
的東西,在邏輯的上下文裡通通被視為 真
。
> (if 27 1 2)
1
邏輯運算子 and
和 or
與條件式類似。兩者都接受任意數量的實參,但僅對能影響返回值的幾個實參求值。如果所有的實參都為 真
(即非 nil
),那麼 and
會返回最後一個實參的值:
> (and t (+ 1 2))
3
如果其中一個實參為 假
,那之後的所有實參都不會被求值。 or
也是如此,只要碰到一個為 真
的實參,就停止對之後所有的實參求值。
這兩個運算子稱為宏。宏和特殊的運算子一樣,可以繞過一般的求值規則。
本作品採用《CC 協議》,轉載必須註明作者和本文連結