Lisp 整體思想

Galois發表於2020-01-19

形式(Form)

人可以透過實踐來學習一件事,這對於 Lisp 來說特別有效,因為 Lisp 是一門互動式的語言。任何 Lisp 系統都含有一個互動式的前端,叫做頂層(toplevel)。你在頂層輸入 Lisp 表示式,而系統會顯示它們的值。
許多 Common Lisp 的實現用 > 作為頂層提示符。

$ clisp
> 1
1
>

列印的值與輸入的值相同,數字 1 稱之為對自身求值。

> (+ 2 3)
5

在表示式 (+ 2 3) 裡, + 稱為運算子,而數字 23 稱為實參。
在 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 對函式呼叫求值時,它做下列兩個步驟:

  1. 首先從左至右對實參求值。在這個例子當中,實參對自身求值,所以實參的值分別是 23
  2. 實參的值傳入以運算子命名的函式。在這個例子當中,將 23 傳給 + 函式,返回 5

如果實參本身是函式呼叫的話,上述規則同樣適用。以下是當 (/ (- 7 1) (- 4 2)) 表示式被求值時的情形:

  1. Lisp 對 (- 7 1) 求值: 7 求值為 7 , 1 求值為 1 ,它們被傳給函式 - ,返回 6 。
  2. Lisp 對 (- 4 2) 求值: 4 求值為 4 , 2 求值為 2 ,它們被傳給函式 - ,返回 2 。
  3. 數值 6 與 2 被傳入函式 / ,返回 3 。

但不是所有的 Common Lisp 運算子都是函式,不過大部分是。函式呼叫都是這麼求值。由左至右對實參求值,將它們的數值傳入函式,來返回整個表示式的值。這稱為 Common Lisp 的求值規則

如果輸入 Lisp 不能理解的東西,它會列印一個錯誤訊息,接著帶你到一種叫做中斷迴圈(break loop)的頂層。輸入 :abort 跳出:

> (/ 1 0)
Error: Division by zero
      Options: :abort, :backtrace
>> :abort
>

一個不遵守 Common Lisp 求值規則的運算子是 quotequote 是一個特殊的運算子,意味著它自己有一套特別的求值規則。這個規則就是:什麼也不做。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)

取出列表元素的基本函式是 carcdr 。對列表取 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

nullnot 做的是一樣的事情。
在 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

邏輯運算子 andor 與條件式類似。兩者都接受任意數量的實參,但僅對能影響返回值的幾個實參求值。如果所有的實參都為 (即非 nil ),那麼 and 會返回最後一個實參的值:

> (and t (+ 1 2))
3

如果其中一個實參為 ,那之後的所有實參都不會被求值。 or 也是如此,只要碰到一個為 的實參,就停止對之後所有的實參求值。
這兩個運算子稱為。宏和特殊的運算子一樣,可以繞過一般的求值規則。

本作品採用《CC 協議》,轉載必須註明作者和本文連結
不要試圖用百米衝刺的方法完成馬拉松比賽。

相關文章