Board logo

標題: [Lisp] Common LISP Hints 中譯 [打印本頁]

作者: weiye    時間: 2006-9-15 02:39     標題: [Lisp] Common LISP Hints 中譯

Common LISP Hints
                          Geoffrey J. Gordon
                         <ggordon@cs.cmu.edu>
                       Friday, February 5, 1993

                            Modified by
                            Bruno Haible
                <haible@ma2s2.mathematik.uni-karlsruhe.de>

                            Transtalted to Traditional Chinese by
                            Wei-Yueh Chen (陳瑋岳)
引用:
Note: This tutorial introduction to Common Lisp was written for the
CMU environment, so some of the details of running lisp toward the end
may differ from site to site.
註:本 Common Lisp 教學文件是針對 CMU 版本的 Lisp ,
所以使用者之間可能會因為採用的 Lisp 版本不同,
在執行細節上有些微差異。
引用:

Further Information
The best LISP textbook I know of is
  Guy L. Steele Jr. _Common LISP: the Language_. Digital Press. 1984.
The first edition is easier to read; the second describes a more recent
standard. (The differences between the two standards shouldn't affect
casual programmers.)

A book by Dave Touretsky has also been recommended to me, although I
haven't read it, so I can't say anything about it.
我所知到最好的 Lisp 教科書是 Guy L. Steele Jr. 所寫的 Common LISP: the Language ,
該書是在 1984. 由 Digital Press 出版社所出版,它的第一版很容易讀,第二版則
描述了更多最新的標準。(對於一般的程式設計師而言,第一、二版關於最新
標準的些微差異並不會有蝦咪影響。)

另外還有一本由 Dave Touretsky 所寫的書也有很多人跟我推薦,不過由於我並沒有去讀過,
所以我也無法評論。
引用:
Symbols

A symbol is just a string of characters. There are restrictions on what
you can include in a symbol and what the first character can be, but as
long as you stick to letters, digits, and hyphens, you'll be safe.
(Except that if you use only digits and possibly an initial hyphen,
LISP will think you typed an integer rather than a symbol.) Some
examples of symbols:
        a
        b
        c1
        foo
        bar
        baaz-quux-garply
Symbols(字符)

symbol 就是一串字元,不過這串字元裡面可以使用的字元跟起始的第一個字元是稍有限制的,
但是原則上只要你使用〝大小寫字母、數字、減號(連接符號)〞來混合產生 symbol,應該就不會有問題。
(如果你只有使用使用數字的,並且至多在拿減號當啟始字元,那將會被 Lisp 視為是數字,而不是 symbol 。)
下面是 symbol 的一些範例:
        a
        b
        c1
        foo
        bar
        baaz-quux-garply
引用:
Some things you can do with symbols follow. (Things after a ">" prompt
are what you type to the LISP interpreter, while other things are what
the LISP interpreter prints back to you. The ";" is LISP's comment
character: everything from a ";" to the end of line is ignored.)
> (setq a 5)            ;store a number as the value of a symbol
5
> a                     ;take the value of a symbol
5
> (let ((a 6)) a)       ;bind the value of a symbol temporarily to 6
6
> a                     ;the value returns to 5 once the let is finished
5
> (+ a 6)               ;use the value of a symbol as an argument to a function
11
> b                     ;try to take the value of a symbol which has no value
Error: Attempt to take the value of the unbound symbol B
你可以像下面的例子一樣的使用 symbol 。
(在 > 提示符號後面的就是你的輸入給 Lisp 直譯器的內容,
 而其他的就是 Lisp 直譯器所送回來輸出的結果。而 ";" 分號
 則是 Lisp 的註解符號,在分號之後到該行結束的資料都會
 被直譯器所忽略。)

> (setq a 5)            ; 把數值 5 存入 a 這個 symbol 裡面。
5
> a                     ; 取得 a 這個 symbol 所存的值。
5
> (let ((a 6)) a)       ; 暫時性地把 a 這個 symbol 的值給設定成 6
6
> a                     ; 當脫離 let 區塊之後, a 的值又變回到 5
5
> (+ a 6)               ; 把 a 這個 symbol 的值當作是加法函數的參數
11
> b                     ; 嘗試著取得並沒有值的 b 這個 symbol 的值看會發生蝦咪事情?
Error: Attempt to take the value of the unbound symbol B
引用:
There are two special symbols, t and nil. The value of t is defined
always to be t, and the value of nil is defined always to be nil. LISP
uses t and nil to represent true and false. An example of this use is
in the if statement, described more fully later:

> (if t 5 6)
5
> (if nil 5 6)
6
> (if 4 5 6)
5

The last example is odd but correct: nil means false, and anything else
means true. (Unless we have a reason to do otherwise, we use t to mean
true, just for the sake of clarity.)
有兩個比較特別的 symbol 就是 t 跟 nil 。
t 這個 symbol 所定義的值就是 t ,而 nil 這個 symbol 所定義
的值就是 nil 。 Lisp 分把把 t 跟 nil 這兩個值拿來表示〝真〞
跟〝偽〞。一個最典型會用的 t 跟 nil 的例子就是 if 函數,
將會更清楚的解釋介紹 if 函數。

> (if t 5 6)
5
> (if nil 5 6)
6
> (if 4 5 6)
5

最後一個例子或許會讓你感到很奇怪,不過它並沒有錯誤。
原因是 nil 表示〝偽〞,而任何其他的值都表示〝真〞。
(除非你有理由要這樣寫程式,不然通常我們還是習慣用
  t 來表示〝真〞,這樣讀程式的時候也比較清楚。)
引用:

Symbols like t and nil are called self-evaluating symbols, because
they evaluate to themselves. There is a whole class of self-evaluating
symbols called keywords; any symbol whose name starts with a colon is a
keyword. (See below for some uses for keywords.) Some examples:

> :this-is-a-keyword
:THIS-IS-A-KEYWORD
> :so-is-this
:SO-IS-THIS
> :me-too
:ME-TOO
像 t 跟 nil 這樣經過評估求值之後依然是本身的 symbol 就稱做
是 self-evaluating symbols (自我評估的 symbol),因為他們經過
評估求值之後依然是本身。實際上,還有一大類的 self-evaluating symbols
會被稱做是 keyword ,就是任何以冒號開頭的 symbol 都稱做是 keyword 。
(皆下來看如何使用 keyword )一些 keyword 例子如下:

> :this-is-a-keyword
:THIS-IS-A-KEYWORD
> :so-is-this
:SO-IS-THIS
> :me-too
:ME-TOO
引用:
Numbers(數字)
An integer is a string of digits optionally preceded by + or -. A real
number looks like an integer, except that it has a decimal point and
optionally can be written in scientific notation. A rational looks like
two integers with a / between them. LISP supports complex numbers,
which are written #c(r i) (where r is the real part and i is the
imaginary part). A number is any of the above. Here are some numbers:
        5
        17
        -34
        +6
        3.1415
        1.722e-15
        #c(1.722e-15 0.75)
Numbers
整數型別的定義就是一連串的數字,並且最前方可以選擇性的
加上+ 或 - 。而實數包含有整數,而且比整數定義廣的是,實數
還可以有小數點,或是也可以用科學記號表示。有理數則是兩個
整數相除而得,也就是在兩個整數中間加上 / 。 Lisp 還支援複數
型別,利用像是 #c(r  i) 這樣表示複數,其中 r 表示複數的實部,
i 表示複數的虛部。上列的任何一種都稱做是 Number (數字)型別。
下面是一些 Number 型別的例子:
        5
        17
        -34
        +6
        3.1415
        1.722e-15
        #c(1.722e-15 0.75)
引用:
The standard arithmetic functions are all available: +, -, *, /, floor,
ceiling, mod, sin, cos, tan, sqrt, exp, expt, and so forth. All of them
accept any kind of number as an argument. +, -, *, and / return a
number according to type contagion: an integer plus a rational is a
rational, a rational plus a real is a real, and a real plus a complex
is a complex. Here are some examples:
> (+ 3 3/4)             ;type contagion
15/4
> (exp 1)               ;e
2.7182817
> (exp 3)               ;e*e*e
20.085537
> (expt 3 4.2)          ;exponent with a base other than e
100.90418
> (+ 5 6 7 (* 8 9 10))  ;the fns +-*/ all accept multiple arguments
There is no limit to the absolute value of an integer except the memory
size of your computer. Be warned that computations with bignums (as
large integers are called) can be slow. (So can computations with
rationals, especially compared to the corresponding computations with
small integers or floats.)
對於數字可以做的運算,一些常見的算數函數如  +, -, *, /, floor,
ceiling, mod, sin, cos, tan, sqrt, exp, expt 都有內建,而且這些
內建的算數函數可以接受任何 Number 型別的參數。 +, -, *, / 這四的
函數的傳回值型別會隨輸入參數的型別而自動延伸為較廣的型別範圍,比如
說整數加上有理數的傳回值,就會是範圍較廣的有理數;而有理數加上實數的
傳回值,是實數;實數加上複數的傳回值,則是複數。下面是一些例子:

> (+ 3 3/4)             ;傳回值型別範圍自動加廣
15/4
> (exp 1)               ;自然對數的基底 e
2.7182817
> (exp 3)               ;e*e*e
20.085537
> (expt 3 4.2)          ;指數函數,以 3 為底數,次方數是 4.2
100.90418
> (+ 5 6 7 (* 8 9 10))  ;內建的 +,-,*,/ 四個算數函數都可以接受多個參數值的呼叫

對於整數絕對值並沒有任何參數大小的限制,完全取決於執行時使用的電腦
記憶體夠不夠。但是要注意,對於大數的計算,越大的數字電腦執行效率一定
越慢。(有理數的計算也是會比較慢,尤其是拿來跟不是很大的整數,還有小數的
計算速度相比較,更明顯。)
引用:
Conses
A cons is just a two-field record. The fields are called "car" and
"cdr", for historical reasons. (On the first machine where LISP was
implemented, there were two instructions CAR and CDR which stood for
"contents of address register" and "contents of decrement register".
Conses were implemented using these two registers.)
Conses are easy to use:
> (cons 4 5)            ;Allocate a cons. Set the car to 4 and the cdr to 5.
(4 . 5)
> (cons (cons 4 5) 6)
((4 . 5) . 6)
> (car (cons 4 5))
4
> (cdr (cons 4 5))
5
Conses(點對)
一個 cons 就是一個有兩個欄位的資料紀錄。由於一些歷史上的
因素,這兩個欄位分別稱作 "car" 跟 "cdr" 。(在第一台實作 Lisp 語言
的機器上, CAR 與 CDR 指令分別表示"Contents of Address Register" 及
"Contents of Decrement Register"。而 cons 就是透過這兩個暫存器而實作的。)
Cons 很容易使用:
> (cons 4 5)            ; 設置一個 cons ,其中 car 設為數字 4 ,而 cdr 設為數字 5 。
(4 . 5)
> (cons (cons 4 5) 6)  ; 設置一個 cons ,其中 car 設為一個點對(4 . 5),而 cdr 設為數字 5 。
((4 . 5) . 6)
> (car (cons 4 5))    ; 取出 (4 . 5) 的 car 設定值。
4
> (cdr (cons 4 5))    ; 取出 (4 . 5) 的 cdr 設定值。
5
引用:
Lists
You can build many structures out of conses. Perhaps the simplest is a
linked list: the car of each cons points to one of the elements of the
list, and the cdr points either to another cons or to nil. You can
create such a linked list with the list fuction:
> (list 4 5 6)
(4 5 6)
Notice that LISP prints linked lists a special way: it omits some of
the periods and parentheses. The rule is: if the cdr of a cons is nil,
LISP doesn't bother to print the period or the nil; and if the cdr of
cons A is cons B, then LISP doesn't bother to print the period for cons
A or the parentheses for cons B. So:
> (cons 4 nil)
(4)
> (cons 4 (cons 5 6))
(4 5 . 6)
> (cons 4 (cons 5 (cons 6 nil)))
(4 5 6)
The last example is exactly equivalent to the call (list 4 5 6). Note
that nil now means the list with no elements: the cdr of (a b), a list
with 2 elements, is (b), a list with 1 element; and the cdr of (b), a
list with 1 element, is nil, which therefore must be a list with no
elements.
The car and cdr of nil are defined to be nil.
If you store your list in a variable, you can make it act like a stack:
> (setq a nil)
NIL
> (push 4 a)
(4)
> (push 5 a)
(5 4)
> (pop a)
5
> a
(4)
> (pop a)
4
> (pop a)
NIL
> a
NIL
Lists(串列)
利用 Cons(點對) 我們可以創造出很多結構,而當中最簡單的,
或許就是(連結串列)。linked list 其實就是把 Cons 的 CAR 指定成
某些元素,而把 CDR 指定到另一個 Cons 或是 NIL 。如下,我們
可以經由 list 函式來創造 linked list 。

> (list 4 5 6)
(4 5 6)

看到上面的例子,你應該有注意到 Lisp 在印 linked list 的時候,會有
一些原則:它輸出的時候會省略掉一些 . 連結點對的點,以及 () 括弧。
而省略的原則如下,如果這個 Cons 的 CDR 是 NIL 的話,那這個 NIL 跟
它前面的連結點將不會被印出來;如果這個 Cons A 的 CDR 是另外一個
Cons B 的話,那在 Cons B 前面的連結點以及 Cons B 本身的小括弧都
不會被印出來。如下例子:

> (cons 4 nil)
(4)
> (cons 4 (cons 5 6))
(4 5 . 6)
> (cons 4 (cons 5 (cons 6 nil)))
(4 5 6)

最後的這個例子,其實跟直接呼叫函數 (list 4 5 6) 是等價的。注意
NIL 的另一個表示意義,其時就是沒有包含任何元素的 list。比如說,
含有兩個元素的 Cons (a b) 的 CDR 是只有一個元素的 Cons (b) ,
而只有一個元素的 Cons (b) 的 CDR 則是沒有任何元素的串列,也
就是 NIL ,由此也可見 NIL 就是沒有包含任何元素的 list (串列)。
而 NIL 的 CAR 跟 CDR 都定義成 NIL 。如果我們把 List 指給任何
變數,那就可以如下當成堆疊(stack)來使用:

> (setq a nil)
NIL
> (push 4 a)
(4)
> (push 5 a)
(5 4)
> (pop a)
5
> a
(4)
> (pop a)
4
> (pop a)
NIL
> a
NIL
引用:
Functions
You saw one example of a function above. Here are some more:
> (+ 3 4 5 6)                   ;this function takes any number of arguments
18
> (+ (+ 3 4) (+ (+ 4 5) 6))     ;isn't prefix notation fun?
22
> (defun foo (x y) (+ x y 5))   ;defining a function
FOO
> (foo 5 0)                     ;calling a function
10
> (defun fact (x)               ;a recursive function
    (if (> x 0)
      (* x (fact (- x 1)))
      1))
FACT
> (fact 5)
120
> (defun a (x) (if (= x 0) t (b (- x))))        ;mutually recursive functions
A
> (defun b (x) (if (> x 0) (a (- x 1)) (a (+ x 1))))
B
> (a 5)
T
> (defun bar (x)                ;a function with multiple statements in
    (setq x (* x 3))            ;its body -- it will return the value
    (setq x (/ x 2))            ;returned by its final statement
    (+ x 4))
BAR
> (bar 6)
13
Functions(函數、函式)
之前我們看過函數的例子了。下面是其他函數的例子:

> (+ 3 4 5 6)                   ; 加法函數可以接受任意多的輸入參數
18
> (+ (+ 3 4) (+ (+ 4 5) 6))     ; 或是你也可以像這樣慢慢加,哈~
22

> (defun foo (x y) (+ x y 5))   ; 定義一個叫做 foo 的函數
FOO
> (foo 5 0)                     ; 呼叫函數,傳入的參數個別是 5 跟 0
10

> (defun fact (x)               ; 以遞迴呼叫的方式定義函數 fact
    (if (> x 0)
      (* x (fact (- x 1)))
      1))
FACT
> (fact 5)
120

> (defun a (x) (if (= x 0) t (b (- x))))        ; 以兩個函數相互呼叫的遞迴方式來定義函數
A
> (defun b (x) (if (> x 0) (a (- x 1)) (a (+ x 1))))
B
> (a 5)
T
> (defun bar (x)                ; 一個函數的定義裡面如果有很多敘述句的話
    (setq x (* x 3))            ; 那整個函數的傳回值,
    (setq x (/ x 2))            ; 將會是最後的一個敘述句
    (+ x 4))
BAR
> (bar 6)
13
引用:
When we defined foo, we gave it two arguments, x and y. Now when we
call foo, we are required to provide exactly two arguments: the first
will become the value of x for the duration of the call to foo, and the
second will become the value of y for the duration of the call. In
LISP, most variables are lexically scoped; that is, if foo calls bar
and bar tries to reference x, bar will not get foo's value for x.
The process of assigning a symbol a value for the duration of some
lexical scope is called binding.
You can specify optional arguments for your functions. Any argument
after the symbol &optional is optional:
> (defun bar (x &optional y) (if y x 0))
BAR
> (defun baaz (&optional (x 3) (z 10)) (+ x z))
BAAZ
> (bar 5)
0
> (bar 5 t)
5
> (baaz 5)
15
> (baaz 5 6)
11
> (baaz)
13
當初我們在定義 foo 函數的時候,要求要兩個傳入值 x 及 y 。
所以每當要呼叫 foo 時,都要恰好給它兩個傳入值,第一個
傳入值將會變成在 foo 函數裡面的 x 變數值,而第二個傳入值
將會變成在 foo 函數裡面的 y 變數值。而在 Lisp 裡面,大多數
變數其實都是 lexically soped ,也就是說,如果 foo 的定義裡面
有呼叫 bar 函數,在 bar 函數被呼叫的時候,依然看不到 foo
裡面 x 變數的值滴。這種指定變數的值所存在的可視範圍,
稱做是 binding (綁定)。
在函數定義的時候,其實有些傳入值可以當作是選用的/非必需
的。(譯註:這應該算多型 polymorphism 的一種吧~:p)
任何傳入值只要在前方加上 &optional 就會變成是選用的/非必需的。
如下例子:
> (defun bar (x &optional y) (if y x 0))
BAR
> (defun baaz (&optional (x 3) (z 10)) (+ x z))
BAAZ
> (bar 5)
0
> (bar 5 t)
5
> (baaz 5)
15
> (baaz 5 6)
11
> (baaz)
13
引用:

It is legal to call the function bar with either one or two arguments.
If it is called with one argument, x will be bound to the value of that
argument and y will be bound to nil; if it is called with two
arguments, x and y will be bound to the values of the first and second
argument, respectively.
The function baaz has two optional arguments. It specifies a default
value for each of them: if the caller specifies only one argument, z
will be bound to 10 instead of to nil, and if the caller specifies no
arguments, x will be bound to 3 and z to 10.
You can make your function accept any number of arguments by ending its
argument list with an &rest parameter. LISP will collect all arguments
not otherwise accounted for into a list and bind the &rest parameter to
that list. So:
> (defun foo (x &rest y) y)
FOO
> (foo 3)
NIL
> (foo 4 5 6)
(5 6)

Finally, you can give your function another kind of optional argument
called a keyword argument. The caller can give these arguments in any
order, because they're labelled with keywords.
> (defun foo (&key x y) (cons x y))
FOO
> (foo :x 5 :y 3)
(5 . 3)
> (foo :y 3 :x 5)
(5 . 3)
> (foo :y 3)
(NIL . 3)
> (foo)
(NIL)
An &key parameter can have a default value too:
> (defun foo (&key (x 5)) x)
FOO
> (foo :x 7)
7
> (foo)
5
你可以使用一或二個參數呼叫 bar 函數。如果你只有用一個參數呼叫 bar 函數,
那個參數就會設定給 x ,而 y 的預設值則會是 NIL ;如果你使用兩個參數呼叫
bar 函數,那 x 跟 y 就分別被設定成第一及第二個傳入參數。
而 baaz 函數有兩個選用參數,並且這兩個參數都有預設值,如果呼叫 baaz 時,
只有給它一個傳入參數,則 z 的直就會是預設值 10 ,而非 NIL ;而如果呼叫
baaz 函數時,沒有給任何傳入值,則 x 跟 z 的值就會是預設值 3 跟 10 。
你可以讓你設計的函數接受任意多個的輸入參數,以要在參數列加上一個 &rest
的參數就可以了, Lisp 將會把所有沒有被指到到變數名稱的參數蒐集再起變成
一個串列,變且把這個串列指定給 &rest 的參數。如下:

> (defun foo (x &rest y) y)
FOO
> (foo 3)
NIL
> (foo 4 5 6)
(5 6)

最後,你還可以幫你的函數設計另一種輸入選用參數的方式,就是透過 keyword 參數。
這種方式的傳入參數沒有前後次序性,因為輸入參數的時候都要指定 keyword 參數的名稱。

> (defun foo (&key x y) (cons x y))
FOO
> (foo :x 5 :y 3)
(5 . 3)
> (foo :y 3 :x 5)
(5 . 3)
> (foo :y 3)
(NIL . 3)
> (foo)
(NIL)

就算是利用 &key 設定的 keyword 參數,也可以有預設值,如下範例:

> (defun foo (&key (x 5)) x)
FOO
> (foo :x 7)
7
> (foo)
5
作者: weiye    時間: 2006-11-15 16:32

引用:
Printing
Some functions can cause output. The simplest one is print, which
prints its argument and then returns it.
> (print 3)
3
3

The first 3 above was printed, the second was returned.
If you want more complicated output, you will need to use format.
Here's an example:
> (format t "An atom: ~S~%and a list: ~S~%and an integer: ~D~%"
          nil (list 5) 6)
An atom: NIL
and a list: (5)
and an integer: 6

The first argument to format is either t, nil, or a stream. T specifies
output to the terminal. Nil means not to print anything but to return a
string containing the output instead. Streams are general places for
output to go: they can specify a file, or the terminal, or another
program. This handout will not describe streams in any further detail.
The second argument is a formatting template, which is a string
optionally containing formatting directives.

All remaining arguments may be referred to by the formatting
directives. LISP will replace the directives with some appropriate
characters based on the arguments to which they refer and then print
the resulting string.

Format always returns nil unless its first argument is nil, in which
case it prints nothing and returns a string.

There are three different directives in the above example: ~S, ~D, and
~%. The first one accepts any LISP object and is replaced by a printed
representation of that object (the same representation which is
produced by print). The second one accepts only integers. The third one
doesn't refer to an argument; it is always replaced by a carriage
return.

Another useful directive is ~~, which is replaced by a single ~.
Refer to a LISP manual for (many, many) additional formatting
directives.
Printing(顯示)
有些函數會導致輸出,而最簡單的輸出,就是透過呼叫 print 函數,
它會把參數給輸出到螢幕上,然後函數的傳回值也是剛剛輸出的結果。
如下例:
> (print 3)
3
3

第一個 3 是因為叫用 print 函數而把參數輸出到螢幕上,第二個 3 則是呼叫函數
之後的傳回值。
如果你希望輸出結果複雜一點,你可以使用 format 函數。見下面範例:
> (format t "An atom: ~S~%and a list: ~S~%and an integer: ~D~%"
          nil (list 5) 6)
An atom: NIL
and a list: (5)
and an integer: 6

在呼叫 format 函數的時候,第一個參數只可以 T, NIL, 或是其他的輸出串流。
其 T 表示要輸出到終端螢幕上, NIL 表示不要輸出任何值,而使要把原本要輸出的
字串當作是函數的傳回值回傳。而如果是其他的輸出串列,則可以指定是任何像是
檔案、終端機、其他程式都可以。此教學講義不會對其他的輸出串流提供更多的解釋,
言謹於此。第二個輸入的參數則是一個格式化的樣板,有就是一個字串,字串裡面可能
含有一些的格式化指令。

其他剩下的參數,則是跟之前字串裡面的格式化指令是相對應的,Lisp 將會把剩下的
參數用來代換至字串裡面相對應的格式化指令。 Lisp 會根據格式化指令的適當屬性,
把其餘參數用適當的方式帶換掉之後,在輸出格式化之後的字串。

Format 函數的傳回值預設會是 NIL ,除非在呼叫 Format 函數的第一個參數是 NIL ,
如此,則不會把格式化之後的字串輸出到任何對象,而是會把格式化之後的字串當作
是函數呼叫的傳回值。

在上面範例裡面用到的三個不同的格式化指令:~S, ~D 跟 ~% 。第一個 ~S 會接受任何的
Lisp 物件,並且會用該物件的可以顯示的方式來取代掉 ~S (當中可以顯示的方式跟直接
利用 print 函數輸出該物件是一樣的方式)。第二個 ~D 會接受任何的整數值。第三個 ~%
則不會被任何之後的輸入參數所取代,可是它會自動轉換成換行的指令。

另外還有一個有用的格式化指令是 ~~ ,它會自動輸出成只有一個 ~ 。
如果還要更多、更多額外的格式化指令,可以參考其他的 Lisp 手冊。
引用:
Forms and the Top-Level Loop
The things which you type to the LISP interpreter are called forms; the
LISP interpreter repeatedly reads a form, evaluates it, and prints the
result. This procedure is called the read-eval-print loop.
Some forms will cause errors. After an error, LISP will put you into
the debugger so you can try to figure out what caused the error. LISP
debuggers are all different; but most will respond to the command
"help" or ":help" by giving some form of help.
In general, a form is either an atom (for example, a symbol, an
integer, or a string) or a list. If the form is an atom, LISP evaluates
it immediately. Symbols evaluate to their value; integers and strings
evaluate to themselves. If the form is a list, LISP treats its first
element as the name of a function; it evaluates the remaining elements
recursively, and then calls the function with the values of the
remaining elements as arguments.
For example, if LISP sees the form (+ 3 4), it treats + as the name of
a function. It then evaluates 3 to get 3 and 4 to get 4; finally it
calls + with 3 and 4 as the arguments. The + function returns 7, which
LISP prints.
The top-level loop provides some other conveniences; one particularly
convenient convenience is the ability to talk about the results of
previously typed forms. LISP always saves its most recent three
results; it stores them as the values of the symbols *, **, and ***.
For example:
> 3
3
> 4
4
> 5
5
> ***
3
> ***
4
> ***
5
> **
4
> *
4
Forms(表單) 跟 the Top-Level Loop(最高層評估值迴圈)
你一行行打字,所輸入給 Lisp 直譯器的那些資料就稱做是表單(forms) ,
Lisp 直譯器會一直讀取你給它的表單,然後進行運算/評估,並且把傳回值
顯示出來,這個一再重複的過程就稱作是個〝讀取(資料)─評估(傳回值)─顯示(傳回值)〞的迴圈。
有些表單可能會導致錯誤(也就是程式碼沒寫好啦),當執行程式的時候
發生錯誤的話, Lisp 會把進入除錯狀態,以便讓我們找出錯誤發生的原因。
每個 Lisp 版本的除錯模式都不太一樣,但是至少當我們對大多數的除錯程式
輸入 "help" 或是 ":help" ,它應該會顯示相關輔助說明文字。

一般而言,表單裡的資料要嘛就是無法再細分的原子(atom),像是字符、整數、
字串....,這些都是屬於無法再細分的原子,要不然表單裡的資料就是一個串列。
如果表單的資料是原子,那 Lisp 通常很快就可以評估出它的傳回值,字符傳回值
就是它所表示的值,整數跟字串的傳回值就是它們本身而已。但如果表單的資料
是一個串列,那 Lisp 會把這個串列的第一個元素當作是函數的名稱,把其他元素
評估完之後的值當作是輸入參數,然後把這整個串列當作是函數呼叫,
舉例來說,如果表單的資料是 (+ 3 4) ,Lisp 會把 + 當作是最後要呼叫的函數
名稱,然後它逐步評估求值,3 評估值(運算)之後是傳回值是 3 ,4 評估值(運算)
之後傳回值是 4 ,而後呼叫 + 這個函數,而傳入 + 這個函數的參數則是剛剛已經
評估完的值 3 跟 4 ,因此呼叫完 + 這個函數的傳回值會是 7 ,最後 Lisp 再把它顯
示給我們看。

譯註:而我們在使用 Lisp 直譯器的時候,位在 > 之後要給它的,就是位在最高層的
〝讀取(資料)─評估(傳回值)─顯示(傳回值)〞的迴圈。

位在最高層的〝讀取─評估─顯示〞的迴圈,其實有其他的好處,其中一個好處
就是可以隨時取出之前運算的表單資料,Lisp 用 *, **, 跟 *** 分別表示在此表單
的前一、二、三個評估值的表單。如下例:
> 3  ; 要評估的表單是 3 ,所以傳回值是 3
3
> 4  ; 要評估的表單是 4 ,所以傳回值是
4
> 5  ; 要評估的表單是 5 ,所以傳回值是 5
5
> ***   ; 要評估的表單是,在這之前推三步的那個表單,所以評估 3 之後,傳回值是 3
3
> ***   ; 要評估的表單是,在這之前推三步的那個表單,所以評估 4 之後,傳回值是 4
4
> ***     ; 要評估的表單是,在這之前推三步的那個表單,所以評估 5 之後,傳回值是 5
5
> **   ; 要評估的表單是,在這之前推兩步的那個表單,所以評估 4 之後,傳回值是 4
4
> *   ; 要評估的表單是,在這之前推一步的那個表單,所以評估 4 之後,傳回值是 4
4
引用:
Special forms
There are a number of special forms which look like function calls but
aren't. These include control constructs such as if statements and do
loops; assignments like setq, setf, push, and pop; definitions such as
defun and defstruct; and binding constructs such as let. (Not all of
these special forms have been mentioned yet. See below.)
One useful special form is the quote form: quote prevents its argument
from being evaluated. For example:
> (setq a 3)
3
> a
3
> (quote a)
A
> 'a                    ;'a is an abbreviation for (quote a)
A
Another similar special form is the function form: function causes its
argument to be interpreted as a function rather than being evaluated.
For example:
> (setq + 3)
3
> +
3
> '+
+
> (function +)
#<Function + @ #x-fbef9de>
> #'+                   ;#'+ is an abbreviation for (function +)
#<Function + @ #x-fbef9de>
The function special form is useful when you want to pass a function as
an argument to another function. See below for some examples of
functions which take functions as arguments.
Special forms (特殊表單)
有一些比較特殊的輸入表單看起來就像是函數呼叫,可是實際上卻不是函數呼叫。
這些特殊表單包含有流程控制命令,如 if 跟 do loop 敘述等,以及用來設定變數的
命令,如 setq, setf, push, 跟 pop ,還有用來定義的命令,如定義函數的 defun 及
定義結構的 defstruct ,還有用來綁定的命令,如 let 。(當然上面並沒有提及所有
的特殊表單,往下看,還會繼續介紹其他的特殊表單。)
最特別的有一個特殊表單 quote 是用來避免它的輸入參數進入評估值的步驟,也
就是它會讓輸入參數以原來的形式當作是傳回值,並不會先經過評估值的步驟。
舉例如下:
> (setq a 3)
3
> a
3
> (quote a)
A
> 'a                    ; 'a 是 (quote a) 的縮寫
A

另外還有一個類似的特殊表單就是 function 表單,function 會讓它的輸入參數被視作
是某個函數,而不是被拿來評估值。
範例如下:
> (setq + 3)
3
> +
3
> '+
+
> (function +)
#<Function + @ #x-fbef9de>
> #'+                   ;#'+ 是 (function +) 的縮寫
#<Function + @ #x-fbef9de>

function 這個特殊表單常常被拿來用在,當你要把函數當作是參數來傳遞的時候。
本文後面會繼續介紹到一些例子,就是把函數拿來當作是輸入參數,此時就會需要
用到 function 這個特殊表單。
引用:
Binding
Binding is lexically scoped assignment. It happens to the variables in
a function's parameter list whenever the function is called: the formal
parameters are bound to the actual parameters for the duration of the
function call. You can bind variables anywhere in a program with the
let special form, which looks like this:
        (let ((var1 val1)
              (var2 val2)
              ...)
          body)
Let binds var1 to val1, var2 to val2, and so forth; then it executes
the statements in its body. The body of a let follows exactly the same
rules that a function body does. Some examples:
> (let ((a 3)) (+ a 1))
4
> (let ((a 2)
        (b 3)
        (c 0))
    (setq c (+ a b))
    c)
5
> (setq c 4)
4
> (let ((c 5)) c)
5
> c
4
Instead of (let ((a nil) (b nil)) ...), you can write (let (a b) ...).
The val1, val2, etc. inside a let cannot reference the variables var1,
var2, etc. that the let is binding. For example,
> (let ((x 1)
        (y (+ x 1)))
    y)
Error: Attempt to take the value of the unbound symbol X
If the symbol x already has a global value, stranger happenings will
result:
> (setq x 7)
7
> (let ((x 1)
        (y (+ x 1)))
    y)
8
The let* special form is just like let except that it allows values to
reference variables defined earlier in the let*. For example,
> (setq x 7)
7
> (let* ((x 1)
         (y (+ x 1)))
    y)
2
The form
        (let* ((x a)
               (y b))
          ...)
is equivalent to
        (let ((x a))
          (let ((y b))
            ...))
Binding(綁定)
綁定是 lexically scoped 的變數值設定。它發生在當函數呼叫時候,參數列的變數
是用綁定的方式設定變數值:在函數呼叫期間,此時此函數定義時的參數列,其值被
綁定在函數呼叫發生時的輸入參數。其實不管在哪程式裡面的哪裡,你也可以利用 let
這個特殊表單來綁定變數值,其使用形式如下:

        (let ((var1 val1)
              (var2 val2)
              ...)
          body)

Let 把 var1 綁定成 val1 ,把 var2 綁定成 val2 ,如此類推,然後它會執行
body 這一區塊的程式敘述。 上面 Let 特殊表單裡面的 body 程式區塊敘述執行
的結果就會像是在函數呼叫時的成是敘述有一樣的效果。如下範例:
(譯註:這像是函數呼叫,只是把參數列改成 let 特殊表單而已,而 body 執行完
之後的傳回值,就會是函數傳回值。)

> (let ((a 3)) (+ a 1)) ; 在 let 表單裡面,綁定 a 為 3,然後執行 a+1 ,傳回值就是 4 。
4

> (let ((a 2)
        (b 3)
        (c 0))
    (setq c (+ a b))
    c)
5

> (setq c 4)
4

> (let ((c 5)) c)
5

> c
4

如果有綁定值是 NIL 的,如 (let ((a nil) (b nil)) ...) ,就可以縮寫成 (let (a b) ...) 。
Let 特殊表單裡面的綁定值 val1, val2 ... 等的值不能參照 var1, var2 ... 等,因為綁定正在
發生,還沒有結束。如下範例:

> (let ((x 1)
        (y (+ x 1)))
    y)
Error: Attempt to take the value of the unbound symbol X

如果變數 x 在上面這段程式執行之前已經有全域變數值,那就會發生很莫名奇妙的結果,
如下範例:

> (setq x 7)
7

> (let ((x 1)
        (y (+ x 1)))
    y)
8

還有一個 let* 也是特殊表單,它跟 let 很像,但是不同的地方是 let* 可以允許
綁定值參考之前已經綁定的變數。如下範例:

> (setq x 7)
7

> (let* ((x 1)
         (y (+ x 1)))
    y)
2

下面這樣的表單

        (let* ((x a)
               (y b))
          ...)

其實就等同於,如下

        (let ((x a))
          (let ((y b))
            ...))
引用:
Dynamic Scoping
The let and let* forms provide lexical scoping, which is what you
expect if you're used to programming in C or Pascal. Dynamic scoping is
what you get in BASIC: if you assign a value to a dynamically scoped
variable, every mention of that variable returns that value until you
assign another value to the same variable.
In LISP, dynamically scoped variables are called special variables. You
can declare a special variable with the defvar special form. Here are
some examples of lexically and dynamically scoped variables.
In this example, the function check-regular references a regular (ie,
lexically scoped) variable. Since check-regular is lexically outside of
the let which binds regular, check-regular returns the variable's
global value.
> (setq regular 5)
5
> (defun check-regular () regular)
CHECK-REGULAR
> (check-regular)
5
> (let ((regular 6)) (check-regular))
5
In this example, the function check-special references a special (ie,
dynamically scoped) variable. Since the call to check-special is
temporally inside of the let which binds special, check-special returns
the variable's local value.
> (defvar *special* 5)
*SPECIAL*
> (defun check-special () *special*)
CHECK-SPECIAL
> (check-special)
5
> (let ((*special* 6)) (check-special))
6
By convention, the name of a special variable begins and ends with a *.
Special variables are chiefly used as global variables, since
programmers usually expect lexical scoping for local variables and
dynamic scoping for global variables.
For more information on the difference between lexical and dynamic
scoping, see _Common LISP: the Language_.
Dynamic Scoping
let 跟 let* 這樣的特殊表單提供了 lexical scoping ,那就像你在寫 C 或是 Pascal 程式
所預期一樣的變數可視範圍。而還有一種 Dynamic scoping 就如同 BASIC 語言所提供的一樣,
如果你指定一個變數值給 dynamically scoped 的變數,那不管你在何時去讀取變數的值,
都會是一開始指定的那個變數值,除非你有給它另一個新變數值,以取代之。
譯註:既,全域變數 ,參考 http://www.supelec.fr/docs/cltl/clm/node67.html

在 Lisp 裡面,這些 dynamically scoped 的變數被稱做是特殊變數(special variables),
你可以透過 defvar 特殊表單來定義特殊變數。下面是一些 lexically 跟 dynamically
scoped 變數的例子。

在下面的這麼範例裡面, check-regular 函數裡面調用了 regular 這個一般變數(亦即
lexically scoped 的變數)。因為 check-regular 函數的定義是在 let 區塊之外,所以 let
區塊裡面的 regular 綁定並不會影響到 check-regular 函數裡面 regular 變數值,所以
check-regular 的傳回值是 regular 變數的全域可視範圍的值。

> (setq regular 5)
5

> (defun check-regular () regular)
CHECK-REGULAR

> (check-regular)
5

> (let ((regular 6)) (check-regular))
5


在下面的這麼範例裡面, check-special 函數裡面調用了 special 這個特殊變數(亦即
dynamically scoped 的變數)。因為在 let 區塊裡面有一段暫時呼叫了 check-special 這個
函數,而且 let 有暫時綁定 special 特殊變數新值,所以 check-special 會傳回的是受到
let 區塊綁定影響的區域變數值。

> (defvar *special* 5)
*SPECIAL*

> (defun check-special () *special*)
CHECK-SPECIAL

> (check-special)
5

> (let ((*special* 6)) (check-special))
6


為了方便記億與區別,通常會把特殊變數的名稱前後會用 * 包圍起來。特殊變數主要
被用在當作是全域變數,因為程式設計師通常會預期區域變數是 lexical scoping ,而
全域變數是 dynamic scoping 。

如果需要更多關於 lexical scoping 跟 dynamic scoping 的區別,請參看
_Common LISP: the Language_ 這本書。
引用:
Arrays
The function make-array makes an array. The aref function accesses its
elements. All elements of an array are initially set to nil. For
example:
> (make-array '(3 3))
#2a((NIL NIL NIL) (NIL NIL NIL) (NIL NIL NIL))
> (aref * 1 1)
NIL
> (make-array 4)        ;1D arrays don't need the extra parens
#(NIL NIL NIL NIL)
Array indices always start at 0.
See below for how to set the elements of an array.

Strings
A string is a sequence of characters between double quotes. LISP
represents a string as a variable-length array of characters. You can
write a string which contains a double quote by preceding the quote
with a backslash; a double backslash stands for a single backslash. For
example:
        "abcd" has 4 characters
        "\"" has 1 character
        "\\" has 1 character
Here are some functions for dealing with strings:
> (concatenate 'string "abcd" "efg")
"abcdefg"
> (char "abc" 1)
#\b                     ;LISP writes characters preceded by #\
> (aref "abc" 1)
#\b                     ;remember, strings are really arrays
The concatenate function can actually work with any type of sequence:
> (concatenate 'string '(#\a #\b) '(#\c))
"abc"
> (concatenate 'list "abc" "de")
(#\a #\b #\c #\d #\e)
> (concatenate 'vector '#(3 3 3) '#(3 3 3))
#(3 3 3 3 3 3)

Structures
LISP structures are analogous to C structs or Pascal records. Here is
an example:
> (defstruct foo
    bar
    baaz
    quux)
FOO
This example defines a data type called foo which is a structure
containing 3 fields. It also defines 4 functions which operate on this
data type: make-foo, foo-bar, foo-baaz, and foo-quux. The first one
makes a new object of type foo; the others access the fields of an
object of type foo. Here is how to use these functions:
> (make-foo)
#s(FOO :BAR NIL :BAAZ NIL :QUUX NIL)
> (make-foo :baaz 3)
#s(FOO :BAR NIL :BAAZ 3 :QUUX NIL)
> (foo-bar *)
NIL
> (foo-baaz **)
3
The make-foo function can take a keyword argument for each of the
fields a structure of type foo can have. The field access functions
each take one argument, a structure of type foo, and return the
appropriate field.
See below for how to set the fields of a structure.
Arrays(陣列)
函數 make-array 可以產生陣列,而函數 aref 則可以純取陣列裡面的元素。
陣列裡所有元素的初始則設定值是 NIL 。如下範例:

> (make-array '(3 3))
#2a((NIL NIL NIL) (NIL NIL NIL) (NIL NIL NIL))

> (aref * 1 1)
NIL

> (make-array 4)        ; 一維陣列的維度不需要額外的小括弧
#(NIL NIL NIL NIL)

陣列的索引值必定是由 0 開始起算。

繼續往下看,將會學到如何設定陣列的元素。

Strings(字串)
所謂的字串就是被兩個 " 所包夾在中間的字元串。 Lisp 實際上是把字串視為是
可變長度的字元陣列。如果要表示的字串裡面本身就包含有 " 的話,那需要在 "
前面加上倒斜線 \ ,而用連續的兩個倒斜線來是表示字串裡面的一個倒斜線。
如下範例:

        "abcd" 包含有 4 個字元
        "\"" 包含有 1 個字元
        "\\" 包含有 1 個字元

下面是一些用來處理字串的函數範例:

> (concatenate 'string "abcd" "efg")  ; 連接字串用 concatenate 函數
"abcdefg"

> (char "abc" 1)
#\b                     ; Lisp 會在字元前面加上 #\ 用來表示字元。

> (aref "abc" 1)
#\b                     ; 請記住,字串其實就是字元陣列而已。

連接字串用的 concatenate 函數實際上可以用來連接任何型別的序列:

> (concatenate 'string '(#\a #\b) '(#\c))
"abc"

> (concatenate 'list "abc" "de")
(#\a #\b #\c #\d #\e)

> (concatenate 'vector '#(3 3 3) '#(3 3 3))
#(3 3 3 3 3 3)


Structures(結構)
Lisp 的結構就類似 C 語言的 struct 跟 Pascal 語言的 record 。下面是一個範例:

> (defstruct foo
    bar
    baaz
    quux)
FOO

這個範例定義了一個名為 foo 的資料型別,這個型別的結構實際上包含了三個欄位。
在定義結構的同時,實際上它也定義了四個可以操作這個資料型別的的函數,分別是
make-foo, foo-bar, foo-baaz, 跟 foo-quux 。第一個函數 make-foo 可以用來產生 foo 資料
型別的物件,而其他三個函數則可以用來取得 foo 資料型別當中對應的資料欄位。
底下是,如何使用這些函數的範例:

> (make-foo)
#s(FOO :BAR NIL :BAAZ NIL :QUUX NIL)

> (make-foo :baaz 3)
#s(FOO :BAR NIL :BAAZ 3 :QUUX NIL)

> (foo-bar *)
NIL

> (foo-baaz **)
3

只要是 foo 結構所有的欄位,在產生物件時候用的 make-foo 函數都可以接受對應欄位的
keyword 參數。而存取資料欄位的取用函數則可以接受一個 foo 物件當作是輸入參數,並且
傳回該結構裡對應的資料欄位之值。

繼續往下看,將會學到如何設定結構裡各欄位的值。
引用:
Setf
Certain forms in LISP naturally define a memory location. For example,
if the value of x is a structure of type foo, then (foo-bar x) defines
the bar field of the value of x. Or, if the value of y is a one-
dimensional array, (aref y 2) defines the third element of y.
The setf special form uses its first argument to define a place in
memory, evaluates its second argument, and stores the resulting value
in the resulting memory location. For example,
> (setq a (make-array 3))
#(NIL NIL NIL)
> (aref a 1)
NIL
> (setf (aref a 1) 3)
3
> a
#(NIL 3 NIL)
> (aref a 1)
3
> (defstruct foo bar)
FOO
> (setq a (make-foo))
#s(FOO :BAR NIL)
> (foo-bar a)
NIL
> (setf (foo-bar a) 3)
3
> a
#s(FOO :BAR 3)
> (foo-bar a)
3
Setf is the only way to set the fields of a structure or the elements
of an array.
Here are some more examples of setf and related functions.
> (setf a (make-array 1))       ;setf on a variable is equivalent to setq
#(NIL)
> (push 5 (aref a 1))           ;push can act like setf
(5)
> (pop (aref a 1))              ;so can pop
5
> (setf (aref a 1) 5)
5
> (incf (aref a 1))             ;incf reads from a place, increments,
6                               ;and writes back
> (aref a 1)
6
Setf

在 Lisp 裡面有某些表單實際上表示的就是記憶體裡的位置,舉例來說,如果 x 是
foo 資料型別的結構的話,那 (foo-bar x) 表示的就是 x 裡面的 bar 資料欄位。
另外,如果 y 是一維陣列,那 (aref y 2) 表示的就是 y 陣列裡面的第三個元素。

而 setf 特殊表單可以接受兩個參數,第一個參數是一個記憶體裡的位置,而第二個參數
在被評估求值之後,所評估出來的值將會被存入第一個參數所指的記憶體位置。舉例如下:

> (setq a (make-array 3))
#(NIL NIL NIL)

> (aref a 1)
NIL

> (setf (aref a 1) 3)
3

> a
#(NIL 3 NIL)

> (aref a 1)
3

> (defstruct foo bar)
FOO

> (setq a (make-foo))
#s(FOO :BAR NIL)

> (foo-bar a)
NIL

> (setf (foo-bar a) 3)
3

> a
#s(FOO :BAR 3)

> (foo-bar a)
3

Setf 是唯一可以用來設定結構裡資料欄位的值,以及設定陣列裡元素之值的方法。

下面是跟 setf 及相關的函數呼叫的一些範例:

> (setf a (make-array 1))       ; setf 作用在單一個變數上面的效果跟 setq 一樣。
#(NIL)

> (push 5 (aref a 1))           ; push 也可以拿來當作是 setf 使用(不過參數順序不太一樣喔!)
(5)

> (pop (aref a 1))              ; 既然 push 可以存值,那 pop 當然就可以取值。
5

> (setf (aref a 1) 5)
5

> (incf (aref a 1))             ; incf 的功用是從記憶體位置讀取出值,然後累加
6                               ; 最後在把累加完之後的值,存回到相同的記憶體位置。

> (aref a 1)
6
引用:
Booleans and Conditionals
LISP uses the self-evaluating symbol nil to mean false. Anything other
than nil means true. Unless we have a reason not to, we usually use the
self-evaluating symbol t to stand for true.
LISP provides a standard set of logical functions, for example and, or,
and not. The and and or connectives are short-circuiting: and will not
evaluate any arguments to the right of the first one which evaluates to
nil, while or will not evaluate any arguments to the right of the first
one which evaluates to t.
LISP also provides several special forms for conditional execution. The
simplest of these is if. The first argument of if determines whether
the second or third argument will be executed:
> (if t 5 6)
5
> (if nil 5 6)
6
> (if 4 5 6)
5
If you need to put more than one statement in the then or else clause
of an if statement, you can use the progn special form. Progn executes
each statement in its body, then returns the value of the final one.
> (setq a 7)
7
> (setq b 0)
0
> (setq c 5)
5
> (if (> a 5)
    (progn
      (setq a (+ b 7))
      (setq b (+ c 8)))
    (setq b 4))
13
An if statement which lacks either a then or an else clause can be
written using the when or unless special form:
> (when t 3)
3
> (when nil 3)
NIL
> (unless t 3)
NIL
> (unless nil 3)
3
When and unless, unlike if, allow any number of statements in their
bodies. (Eg, (when x a b c) is equivalent to (if x (progn a b c)).)
> (when t
    (setq a 5)
    (+ a 6))
11

More complicated conditionals can be defined using the cond special
form, which is equivalent to an if ... else if ... fi construction.
A cond consists of the symbol cond followed by a number of cond
clauses, each of which is a list. The first element of a cond clause is
the condition; the remaining elements (if any) are the action. The cond
form finds the first clause whose condition evaluates to true (ie,
doesn't evaluate to nil); it then executes the corresponding action and
returns the resulting value. None of the remaining conditions are
evaluated; nor are any actions except the one corresponding to the
selected condition. For example:
> (setq a 3)
3
> (cond
   ((evenp a) a)        ;if a is even return a
   ((> a 7) (/ a 2))    ;else if a is bigger than 7 return a/2
   ((< a 5) (- a 1))    ;else if a is smaller than 5 return a-1
   (t 17))              ;else return 17
2
If the action in the selected cond clause is missing, cond returns what
the condition evaluated to:
> (cond ((+ 3 4)))
7
Here's a clever little recursive function which uses cond. You might be
interested in trying to prove that it terminates for all integers x at
least 1. (If you succeed, please publish the result.)
> (defun hotpo (x steps)        ;hotpo stands for Half Or Triple Plus One
    (cond
     ((= x 1) steps)
     ((oddp x) (hotpo (+ 1 (* x 3)) (+ 1 steps)))
     (t (hotpo (/ x 2) (+ 1 steps)))))
A
> (hotpo 7 0)
16
The LISP case statement is like a C switch statement:
> (setq x 'b)
B
> (case x
   (a 5)
   ((d e) 7)
   ((b f) 3)
   (otherwise 9))
3
The otherwise clause at the end means that if x is not a, b, d, e, or
f, the case statement will return 9.
Booleans(布林值) and Conditionals(控制判斷條件)
Lisp 使用其值為本身的 NIL 表示〝偽〞。任何其他不是 NIL 的值都表示真。
然而除非有特殊理由要這樣處理,不然我們還是會習慣上利用其值為本身的 T
表示〝真〞。
Lisp 提供了一系列的標準的邏輯函數,比如像是 and, or 以及 not 函數。
and 以及 or 函數是屬於 short-circuit ,也就是說,如果 and 函數的有任何
一個個參數的運算結果已經是 NIL ,拿之後的參數將不用進行運算估值;而 or
函數如果有任何一個參數運算結果事 T ,那之後的參數就不會進行運算估值。
Lisp 也提供了幾個特殊表單用來做控制判斷執行的條件。最簡單的就是 if 敘述
,在 if 敘述的第一個參數將會決定,接下來執行的會是第二個或是第三個參數。
> (if t 5 6)
5
> (if nil 5 6)
6
> (if 4 5 6)
5
如果你在 if 敘述之後的 then(第二個參數) 或是 else(第三個參數) 的部份想要
執行超過一個以上的敘述,那你可以使用 progn 這個特殊表單。 progn 將會執行
在它內部的每一個敘述,並且傳回最後一個評估值之後的結果。
> (setq a 7)
7
> (setq b 0)
0
> (setq c 5)
5
> (if (> a 5)
    (progn
      (setq a (+ b 7))
      (setq b (+ c 8)))
    (setq b 4))
13
if 敘述如果缺乏 then(第二個參數) 或是 else(第三個參數) 的部份,其實也可以
用 when 或是 unless 特殊表單改寫,如下範例:
> (when t 3)
3
> (when nil 3)
NIL
> (unless t 3)
NIL
> (unless nil 3)
3
when 跟 unless 特殊表單並不像 if 只可以放一個敘述,他們可以放任一個數的
敘述在他們內部當作參數。(例如: (when x a b c) 就等價於 (if x (progn a b c)) 。 )
> (when t
    (setq a 5)
    (+ a 6))
11

更複雜的控制判斷條件可以透過 cond 特殊表單來處裡, cond 特殊表單相當於
if ... else if ... fi 控制判斷條件一樣。

cond 特殊表單包含有開頭的 cond 字符,後面接的一連串的判斷子句,每一個
判斷句都是一個串列,該串列的第一個元素就是判斷條件,而剩下的元素(如果有的話)
就是要有可能要執行的敘述句。 cond 特殊表單會找尋第一個滿足判斷條件為真(也就是,
不是 NIL)的子句,然後執行該子句裡面對應的敘述句,並且把運算評估完的結果當作是
傳回值。而剩下的其他子句就不會被運行評估了, cond 特殊表單只會運行至多一個符合
判斷結果為真的子句敘述。如下範例:

> (setq a 3)
3
> (cond
   ((evenp a) a)        ;如果(if) a 是偶數,則傳回值為 a
   ((> a 7) (/ a 2))    ;不然,如果(else if) a 比 7 大,則傳回值為 a/2
   ((< a 5) (- a 1))    ;不然,如果(else if) a 比 5 小,則傳回值為 a-1
   (t 17))              ;不然(else),傳回值為 17
2

如果在 cond 特殊表單裡面,判斷條件為真且要執行的那個子句,並沒有要執行的
敘述句部分的話,那 cond 表單就會傳回判斷條件為真的那個結果。如下:

> (cond ((+ 3 4)))
7

接下來是一個用到 cond 特殊表單的遞迴函數定義的巧妙小例子。你或許可以試著
証明看任何 x 以比 1 大的整數值帶入,最後這個遞迴函數都會終止。(如果你成功
證明出來了,請務必要昭告天下!)
(譯註:這是數學界有名的 3x+1 猜想,至 2006 年目前依然無人成功証出。)

> (defun hotpo (x steps)        ; hotpo 會把偶數減半,把奇數乘三後加一
    (cond
     ((= x 1) steps)
     ((oddp x) (hotpo (+ 1 (* x 3)) (+ 1 steps)))
     (t (hotpo (/ x 2) (+ 1 steps)))))
A
> (hotpo 7 0)                   ; 從 7 經 hotpo 運算到 1 共要經過 16 步。
16

Lisp 也有一個 case 敘述句,就類似 C 語言的 switch 敘述句一樣。如下範例:

> (setq x 'b)
B
> (case x
   (a 5)     ; 如果 x 是 a ,那傳回值就是 5
   ((d e) 7)    ; 如果 x 是 d 或 e ,那傳回值就是 7
   ((b f) 3)    ; 如果 x 是 b 或 f ,那傳回值就是 3
   (otherwise 9))   ; 此外,那傳回值就是 9
3

最後的 otherwise 子句,所表示的意思是〝如果 x 不是 a, b, d, e, 或是 f ,那傳回值就是 9 。〞
引用:

Iteration
The simplest iteration construct in LISP is loop: a loop construct
repeatedly executes its body until it hits a return special form. For
example,
> (setq a 4)
4
> (loop
   (setq a (+ a 1))
   (when (> a 7) (return a)))
8
> (loop
   (setq a (- a 1))
   (when (< a 3) (return)))
NIL
The next simplest is dolist: dolist binds a variable to the elements of
a list in order and stops when it hits the end of the list.
> (dolist (x '(a b c)) (print x))
A
B
C
NIL
Dolist always returns nil. Note that the value of x in the above
example was never nil: the NIL below the C was the value that dolist
returned, printed by the read-eval-print loop.
The most complicated iteration primitive is called do. A do statement
looks like this:

> (do ((x 1 (+ x 1))
       (y 1 (* y 2)))
      ((> x 5) y)
    (print y)
    (print 'working))

1
WORKING
2
WORKING
4
WORKING
8
WORKING
16
WORKING
32

The first part of a do specifies what variables to bind, what their
initial values are, and how to update them. The second part specifies a
termination condition and a return value. The last part is the body. A
do form binds its variables to their initial values like a let, then
checks the termination condition. As long as the condition is false, it
executes the body repeatedly; when the condition becomes true, it
returns the value of the return-value form.

The do* form is to do as let* is to let.
Iteration(重複結構)

在 Lisp 中最簡單的重複結構就是 loop(迴圈) 了: loop (迴圈)結構會一再重複執行
其內部的指令,直到執行到 return 特殊表單才會結束。如下範例:

> (setq a 4)
4

> (loop
   (setq a (+ a 1))
   (when (> a 7) (return a)))
8

> (loop
   (setq a (- a 1))
   (when (< a 3) (return)))
NIL

下一個最簡單的重複結構就是 dolist : dolist 會把變數依序綁值於串列裡面的所有
元素,直到把達到串列底部沒有元素才結束。如下範例:

> (dolist (x '(a b c)) (print x))
A
B
C
NIL

Dolist 的傳回值必定是 NIL 。請注意看上面範例裡面 x 綁訂的值卻從未是 NIL ,
在 C 後面的 NIL 是 dolist 的傳回值,也就是要滿足 〝讀取─評估─顯示〞迴圈必定會
顯示的評估(運算)值。

最複雜的重複結構主要就是 do 迴圈了。一個 do 迴圈的範例看起來就像下面這樣:

> (do ((x 1 (+ x 1))
       (y 1 (* y 2)))
      ((> x 5) y)
    (print y)
    (print 'working))
1
WORKING
2
WORKING
4
WORKING
8
WORKING
16
WORKING
32

在上面範例裡面,在 do 迴圈的後面的第一個大區塊裡的是變數名稱,以及該變數綁定的
初始值,還有每次迴圈運行一圈之後,變數的更新條件。第二個大區塊裡的則是 do 迴圈的
終止條件,以及 do 迴圈結束之後的傳回值。(譯註:此終止條件是在每次進入迴圈主體前
檢查,也就是迴圈主體可能會連一次都沒有被執行到。)最後一個大區塊,則是迴圈主體。
do 表單會先如同 let 特殊表單依樣綁定變數初始值,然後檢查迴圈終止條件是否成立,
只要每次檢查終止條件不成立,那就會執行迴圈主體,然後再回到檢查終止條件地部份,
直到檢查到終止條件成立,則傳回當初在第二大區塊的所指定的傳回值。

另外還有一個 do* 表單,功能如同上面的 do 表單,只是相對於把上面敘述的 let 改成
let* 而已。

Non-local Exits
The return special form mentioned in the section on iteration is an
example of a nonlocal return. Another example is the return-from form,
which returns a value from the surrounding function:
> (defun foo (x)
    (return-from foo 3)
    x)
FOO
> (foo 17)
3
Actually, the return-from form can return from any named block -- it's
just that functions are the only blocks which are named by default. You
can create a named block with the block special form:
> (block foo
    (return-from foo 7)
    3)
7
The return special form can return from any block named nil. Loops are
by default labelled nil, but you can make your own nil-labelled blocks:
> (block nil
    (return 7)
    3)
7
Another form which causes a nonlocal exit is the error form:
> (error "This is an error")
Error: This is an error
The error form applies format to its arguments, then places you in the
debugger.
引用:
Funcall, Apply, and Mapcar
Earlier I promised to give some functions which take functions as
arguments. Here they are:
> (funcall #'+ 3 4)
7
> (apply #'+ 3 4 '(3 4))
14
> (mapcar #'not '(t nil t nil t nil))
(NIL T NIL T NIL T)
Funcall calls its first argument on its remaining arguments.
Apply is just like funcall, except that its final argument should be a
list; the elements of that list are treated as if they were additional
arguments to a funcall.
The first argument to mapcar must be a function of one argument; mapcar
applies this function to each element of a list and collects the
results in another list.
Funcall and apply are chiefly useful when their first argument is a
variable. For instance, a search engine could take a heuristic function
as a parameter and use funcall or apply to call that function on a
state description. The sorting functions described later use funcall
to call their comparison functions.
Mapcar, along with nameless functions (see below), can replace many
loops.

Lambda
If you just want to create a temporary function and don't want to
bother giving it a name, lambda is what you need.
> #'(lambda (x) (+ x 3))
(LAMBDA (X) (+ X 3))
> (funcall * 5)
8
The combination of lambda and mapcar can replace many loops. For
example, the following two forms are equivalent:
> (do ((x '(1 2 3 4 5) (cdr x))
       (y nil))
      ((null x) (reverse y))
    (push (+ (car x) 2) y))
(3 4 5 6 7)
> (mapcar #'(lambda (x) (+ x 2)) '(1 2 3 4 5))
(3 4 5 6 7)

Sorting
LISP provides two primitives for sorting: sort and stable-sort.
> (sort '(2 1 5 4 6) #'<)
(1 2 4 5 6)
> (sort '(2 1 5 4 6) #'>)
(6 5 4 2 1)
The first argument to sort is a list; the second is a comparison
function. The sort function does not guarantee stability: if there are
two elements a and b such that (and (not (< a b)) (not (< b a))), sort
may arrange them in either order. The stable-sort function is exactly
like sort, except that it guarantees that two equivalent elements
appear in the sorted list in the same order that they appeared in the
original list.
Be careful: sort is allowed to destroy its argument, so if the original
sequence is important to you, make a copy with the copy-list or copy-seq
function.

Equality
LISP has many different ideas of equality. Numerical equality is
denoted by =. Two symbols are eq if and only if they are identical. Two
copies of the same list are not eq, but they are equal.
> (eq 'a 'a)
T
> (eq 'a 'b)
NIL
> (= 3 4)
NIL
> (eq '(a b c) '(a b c))
NIL
> (equal '(a b c) '(a b c))
T
> (eql 'a 'a)
T
> (eql 3 3)
T
The eql predicate is equivalent to eq for symbols and to = for numbers
or the same type:
> (eql 2.0 2)
NIL
> (= 2.0 2)
T
> (eq 12345678901234567890 12345678901234567890)
NIL
> (= 12345678901234567890 12345678901234567890)
T
> (eql 12345678901234567890 12345678901234567890)
T
The equal predicate is equivalent to eql for symbols and numbers. It is
true for two conses if and only if their cars are equal and their cdrs
are equal. It is true for two structures if and only if the structures
are the same type and their corresponding fields are equal.
Funcall, Apply, and Mapcar (函數當作參數)

在本文前半塊,我曾說過要給幾個把函數名稱當作是函數傳入參數的例子。舉例如下:

> (funcall #'+ 3 4)
7

> (apply #'+ 3 4 '(3 4))
14

> (mapcar #'not '(t nil t nil t nil))
(NIL T NIL T NIL T)

funcall 會呼叫以第一個參數為名的函數,並把 funcall 的其他參數當作是要呼叫的
函數的傳入參數。

apply 就像是 funcall 一樣的功用,除了 apply 的最後一個參數必須要是串列;這
最後串列裡面的元素,就像是在使用 funcall 時的額外參數一樣。

mapcar 的第一個參數必須是可以作用於單一傳入值的函數名稱, mapcar 會把該函數
名稱套用在,其後參數串列的每一個元素上,並且把函數呼叫結果集合起來,形成新的
串列回傳。

funcall 跟 apply 就是因為他們的第一個參數可以是變數,所以特別有用。舉例應用如,
當一個搜尋引擎可以採用啟發式的函數當作是參數,並且利用 funcall 或 apply 把那個
函數參數作用在狀態敘述上。稍後會介紹的排序函數,也是利用 funcall 來傳遞排序時
要用的哪個比較函數來比較大小。

mapcar 跟未具名函數(後面會介紹)一起使用,可以取代掉很多迴圈的使用。

Lambda (未具名函數)

如果你想要創造一個暫時性使用的函數,並且不想煩惱應該給那個函數什麼名稱,此時就
可以使用 lambda (未具名函數)。

> #'(lambda (x) (+ x 3))
(LAMBDA (X) (+ X 3))

> (funcall * 5)      ; 譯註: * 表示前一個輸入表單,在此就是 #'(lambda (x) (+ x 3))
8

把 lambda 跟 mapcar 一起組合使用可以取代掉大多數的迴圈的使用。如下範例,下面的兩個
表單是等價的。

> (do ((x '(1 2 3 4 5) (cdr x))
       (y nil))
      ((null x) (reverse y))
    (push (+ (car x) 2) y))
(3 4 5 6 7)

> (mapcar #'(lambda (x) (+ x 2)) '(1 2 3 4 5))
(3 4 5 6 7)


Sorting(排序)

Lisp 提供了兩個主要的排序函數: sort 跟 stable-sort 。

> (sort '(2 1 5 4 6) #'<)
(1 2 4 5 6)

> (sort '(2 1 5 4 6) #'>)
(6 5 4 2 1)

sort 的第一個參數是一個串列,而第二個參數則是一個比較大小用的比較函數的名稱。
sort 函數並不保證排序的穩定性,也就是說,如果有兩個元素 a 與 b 滿足
(and (not (< a b)) (not (< b a))) (譯註:也就是 a 與 b 套用在排序函數時相等),
sort 或許有可能在排序之後,會對調 a 與 b 的順序。而 stable-sort 跟 sort 使用方式
完全一樣,除了 stable-sort 保證對於相同的元素必定不會對調順序。

請務必注意: sort 允許破壞他的輸入參數序列,所以如果原始傳入參數對你而言是很重要的,
請先利用 copy-list 或 copy-seq 做好備份。


Equality(相等)

Lisp 對於"相等"的意義有很多種類型。 數值上的相等是用 = 來判別。兩個字符則是用 eq 來
檢查他們是否是同一個。兩個有相同值的串列拷貝並不是 eq 的(譯註:不同的記憶體位置),但這
兩個有相同值的串列拷貝卻是 equal 的(譯註:儲存的數據是一樣的)。

> (eq 'a 'a)
T

> (eq 'a 'b)
NIL

> (= 3 4)
NIL

> (eq '(a b c) '(a b c))
NIL

> (equal '(a b c) '(a b c))
T

> (eql 'a 'a)
T

> (eql 3 3)
T

eql 判斷式等價於 "判斷是否是相同型別" 加上 "如果同是字符,判斷是否 eq " 再加上
"如果同是數值,判斷是否 = "的合體。

> (eql 2.0 2)
NIL

> (= 2.0 2)
T

> (eq 12345678901234567890 12345678901234567890)
NIL

> (= 12345678901234567890 12345678901234567890)
T

> (eql 12345678901234567890 12345678901234567890)
T

用在 字符跟數值上, equal 判斷式就等價於 eql 。對於兩個 cons 而言,如果他們的 car
跟 cdr 都是 equal ,那這兩個 cons 就是 equal 的。對於兩個 structures (結構) 而言,
如果他們有相同的資料型別,並且相對應的資料欄位是 equal 的,那這兩個結構就是 equal 的。
引用:
Some Useful List Functions
These functions all manipulate lists.
> (append '(1 2 3) '(4 5 6))    ;concatenate lists
(1 2 3 4 5 6)
> (reverse '(1 2 3))            ;reverse the elements of a list
(3 2 1)
> (member 'a '(b d a c))        ;set membership -- returns the first tail
(A C)                           ;whose car is the desired element
> (find 'a '(b d a c))          ;another way to do set membership
A
> (find '(a b) '((a d) (a d e) (a b d e) ()) :test #'subsetp)
(A B D E)                       ;find is more flexible though
> (subsetp '(a b) '(a d e))     ;set containment
NIL
> (intersection '(a b c) '(b))  ;set intersection
(B)
> (union '(a) '(b))             ;set union
(A B)
> (set-difference '(a b) '(a))  ;set difference
(B)
Subsetp, intersection, union, and set-difference all assume that each
argument contains no duplicate elements -- (subsetp '(a a) '(a b b)) is
allowed to fail, for example.
Find, subsetp, intersection, union, and set-difference can all take a
:test keyword argument; by default, they all use eql.
一些好用的串列處理函數

下面是一些用來操作串列的有用函數。

> (append '(1 2 3) '(4 5 6))    ; 連結許多串列
(1 2 3 4 5 6)

> (reverse '(1 2 3))            ; 逆轉一個串列裡面的元素
(3 2 1)

> (member 'a '(b d a c))        ; 集合元素的"屬於"判斷 -- 它會傳回第一個找到的元素
(A C)                           ; 至後方所有元素所形成的串列,也就是找第一個 car 是該元素的串列
                                ; 譯註:空串列NIL 即為偽,其他任何非空串列皆表示真。
        
> (find 'a '(b d a c))          ; 另一個檢查元素是否屬於該集合的方法就是用 find 。
A

> (find '(a b) '((a d) (a d e) (a b d e) ()) :test #'subsetp)
(A B D E)                       ; find 是很有彈型的,可以傳入要用來判斷的函數。
                                    ; 上面例子就是改用 subsectp (檢查是否為子集合) 來找尋滿足條件的集合。

> (subsetp '(a b) '(a d e))     ; 檢查是否為子集合
NIL

> (intersection '(a b c) '(b))  ; 求集合的交集
(B)

> (union '(a) '(b))             ; 求集合的聯集
(A B)

> (set-difference '(a b) '(a))  ; 求差集合
(B)

Subsetp, intersection, union, 和 set-difference 都有一個基本假設就是傳入值的參數串列內
不會有重複的元素(也就是集合),不然的話,像是 (subsetp '(a a) '(a b b)) 判斷出來的傳回值
就可能是偽。

Find, subsetp, intersection, union, 和 set-difference 都可以加上 :test 這一個 keyword 參數,
用以改變判斷條件,而如果沒有使用 :test 改寫判斷條件的話,預設就是使用 eql 當作是判斷條件。

=.= 慢慢來~不要急~ :P
























Getting Started with Emacs
You can use Emacs to edit LISP code: most Emacses are set up to enter
LISP mode automatically when they find a file which ends in .lisp, but
if yours isn't, you can type M-x lisp-mode.
You can run LISP under Emacs, too: make sure that there is a command in
your path called "lisp" which runs your favorite LISP. For example, you
could type
        ln -s /usr/local/bin/clisp ~/bin/lisp
Then in Emacs type M-x run-lisp. You can send LISP code to the LISP you
just started, and do all sorts of other cool things; for more
information, type C-h m from any buffer which is in LISP mode.
Actually, you don't even need to make a link. Emacs has a variable
called inferior-lisp-program; so if you add the line
        (setq inferior-lisp-program "/usr/local/bin/clisp")
to your .emacs file, Emacs will know where to find CLISP when
you type M-x run-lisp.




歡迎光臨 Math Pro 數學補給站 (https://math.pro/db/) 論壇程式使用 Discuz! 6.1.0