1 |
(TOPIC-ABOUT ((COMP LIST CONS) (COMP APPLY MAPCAR))) |
關於lisp的爭論太多了,有人對其讚美到了無以復加的地步,也有人覺得不以為然,但是作為一個沒有深入學習過的人來說,就只能做一個不明真相的圍觀群眾。而對於那些躍躍欲試想要參與進來的初學者來說,想要入門卻並非易事,一方面面對高手們撲面而來的那些玄之又玄的大道,另一方面還要面對lisp本身“奇異”的表達形式以及最致命的環環相扣的括號。因而選擇一個合適的入門材料就顯得更為重要→,有人推薦SICP等一些大部頭著作,但是這些作為深入學習研究來說比較適合,如果作為熟悉語法特性等,還是直接動手開始coding比較合適,Common Lisp Koans就是一個很好的選擇,這個專案模仿Ruby Koans通過填補程式碼來熟悉Lisp的語法及特性。
在mapcar-and-reduce.lsp
一節,用 apply 與 mapcar 實現轉置矩陣的功能,在Lisp中函式也可以當做引數傳遞給其它函式使用,其中內建可以以函式作為引數的函式有:
1 2 3 4 5 6 |
> (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 的第一個引數為執行函式(將第一個引數稱為“執行函式”),剩餘引數作為該函式的引數;
- apply 與 funcall 的作用一樣,只是其最後一個引數必須為list;
- mapcar 接受第一個引數作為執行函式,然後將第二個list中的每一個引數依次傳遞給執行函式,並將執行結果依次合併為一個list輸出。
奇怪的是 apply 與 funcall 之間的區別僅僅是 apply 的引數必須是 list,那麼 apply 的功能是如何實現的?
原來 apply 先對執行函式以外引數呼叫 list* 方法,然後再對生成的列表呼叫 values-list 方法,最終再將 values-list 輸出的結果依次傳入執行函式中。這裡 (list* args) 的執行過程如下:
- 如果 args 是 list 型別,則返回 args;
- 如果 args 是單個數字,則返回 args;
- 如果 args 是多個元素(非 list 形式),則將倒數第二個元素與最後一個元素以 dot-list 的形式結合並返回整個列表。
1 2 3 4 5 6 |
> (list* '(1 2 3)) (1 2 3) > (list* 1 2 3) (1 2 . 3) > (list* 1) 1 |
這裡又涉及到點狀列表(Dotted Lists)的概念:
呼叫 list 所構造的列表,這種列表精確地說稱之為正規列表(properlist )。一個正規列表可以是 NIL 或是 cdr 是正規列表Cons 物件…一個非正規列表的 Cons 物件稱之為點狀列表 (dotted list)。
可以看到,list* 的作用主要在最後一個引數上,它將最後一個元素以 append 的方式連線到前面的元素所組成的列表中(這樣做的意義待考?),而 values-list 執行的操作則是將引數列表中的每一個元素依次返回,但傳入引數不能是dot-list,這就導致了 apply 的最後一個引數必須是 list 型別的特性(這樣做的意義也待考?)。
apply 與 mapcar 的組合如何完成轉置矩陣的功能呢?還要依賴於 Lisp 函式引數的引數列表(Parameter Lists)機制,
1 2 3 4 |
> ((lambda (&rest r) r) '(1 2 3)) ((1 2 3)) > ((lambda (&rest r) r) 1 2 3) (1 2 3) |
形參中的 &rest 關鍵詞收集所有剩餘引數,並存放到一個列表裡(list r)
。將 mapcar 與上面的匿名函式相結合會得到什麼樣的結果呢?
1 2 3 4 |
> (mapcar (lambda (&rest r) r) '(1 2 3)) ((1) (2) (3)) > (mapcar (lambda (&rest r) r) '(1 2 3) '(4 5 6)) ((1 4) (2 5) (3 6)) |
上面的方法已經得到了轉置的效果,mapcar 將後續列表中的元素逐一傳入匿名函式,而匿名函式通過 &rest 接收多餘的引數,這樣就可以無限制地新增引數,而通過 mapcar 遍歷每個引數列表的元素,經由匿名函式(list r)
組裝後返回,從而實現轉置的效果。而 apply 的作用則是利用 list* 與 values-list 將矩陣的每一行作為輸入列表依次傳遞給上面的過程:
1 2 |
> (apply #'mapcar (lambda (&rest r) r) '((1 2 3) (4 5 6) (7 8 9))) ((1 4 7) (2 5 8) (3 6 9)) |
總結
Lisp Koans 很適合學習,實際上有人整理了一個 Koans 系列:Learn a New Programming Language Today with Koans,涵蓋了許多程式語言。另外,關於 Lisp,有一篇The Nature of Lisp(翻譯版:Lisp的本質)很值得初學者拜讀。