他是圖靈在普林斯頓時的老師,是當時一位頂尖的數學家與計算理論專家。當一個文化進入另一個時,首先需要做的是將原有的語彙翻譯過去。然而在台灣的電腦科學界,我找不到這個人名字的翻譯。他叫做 Alonzo Church,在對岸的文章中稱他為:阿隆佐,邱奇,因此,我文章以 Church 來稱呼他。
在那個時代裡,還沒有現代的電腦,那時人類還在探索用機器進行數學計算的可能性。而 Church 正是投下第一彈的人,提出了將數學函式抽象化的原則:lambda calculus。
從這時候開始,人類開始討論的不是函式怎麼算出來,而是函式的抽象變化與應用。lambda calculus 的概念也形塑了最早期的程式語言模型。
回到現代,這個時代最多人用的語言,我想應該是 JavaScript,網頁前端的人得用,有時候網站後端的人也得用。因此,我們來看兩個函式的宣告:
var square = function(n) {
return n * n;
}
以上的函式,與以下的函式在語義上是等價的。
function square(n) {
return n * n;
}
這件事我想應該很多人能快速理解它,藉著這個觀念,我們接下來要來看 Racket 的函式宣告:
(define square
(lambda (n)
(* n n)))
同樣,以上的函式與以下的函式,在語義上也是等價的:
(define (square n)
(* n n))
Racket 的函式宣告,最基本的概念是 define
一個 lambda
給 id
的過程。因此,你若在 Chez Scheme (最好的 Scheme 實作) 的 source code 看看它裡頭使用的函式宣告,幾乎清一色是用 lambda
的宣告風格。
然而,第二種宣告方式,其實是更多人慣用的方式,用 ()
將函式的名稱與參數包起來,接下來就是接函式定義的本體,不使用 lambda
。我猜應該是這樣寫,打的字比較少,所以更多人愛用吧!
現在,你會了基本的函式宣告,也看到上面示範的 square
了,你試著來宣告 cube
(立方)吧。
很多語言其實是沒有規格書的,但 Scheme 不只有,而且言簡意賅,頁數極少。在 Scheme 規格書裡,第一章介紹整個 Scheme 語言的概覽,第一章的第一小節介紹 Scheme 的資料型態,第一小節的最後一項,介紹函式(原文稱 Procedure),他是這麼說的:
Procedures Procedures are values in Scheme. [1]
是的,在 Scheme 與 Racket 裡面,函式不是像值,函式就是值。因為它是值,在語義上,可以進行多種變化與應用。
剛剛上頭,我留下一個練習,要各位寫一個 cube
的函式。我們現在來想想,怎樣在 Racket 裡頭做連加?假設我有一組連續數字:0 ~ n,我要把它連加起來,不使用公式解,使用 Functional Programming Language 標準的遞迴解,應該怎麼寫呢?
(define sum
(lambda (n)
(if (= n 0)
0
(+ n (sum (- n 1))))))
這裡引入一個過幾天會談到的 if
,if
也是一個函式,它有三個關鍵點,if
後方直接接判斷式,做完判斷後,#t
的結果放接下來第一個位置,#f
的結果放接下來第二個位置。
這樣寫很蠢,沒關係,我們再看連乘怎麼寫,給定我有一個連續數字:0 到 n,求其連乘解(前提是 0! = 1
):
(define factor
(lambda (n)
(if (= n 0)
1
(* n (factor (- n 1))))))
這裡的操作,都是一樣的,連加與連乘 0 到 n,就是指 n 加上(或乘上)它子集的連加(或連乘)。
然而,FP 語言特性便是如此,你發現流程有重複嗎?有的,同樣要判斷值是否為 0,同樣要用 n 來加上(或乘上)它以下的值的函式結果。我們可不可以這樣寫:
(define num-proc
(lambda (n init proc)
(if (= n 0)
init
(proc n (num-proc (- n 1))))))
然後把 sum
與 factor
改為這樣:
(define another-sum
(lambda (n)
(num-proc n 0 +)))
(define another-factor
(lambda (n)
(num-proc n 1 *)))
在這範例裡頭,各位會看到,我們之前所用的 +
與 *
全部被當參數傳了!在 Racket 與 Scheme 裡頭,它們全都是函式!這也就是為何我們在第三天時,直接打 1 + 1
時,會出現:
1
#<procedure:+>
1
的緣故。好,最後一個變化,使用我們前面的 square
與 cube
,並加上一個 identify
。
(define identify
(lambda (n) n))
(define cube
(lambda (n)
(* n n n)))
(define new-num-proc
(lambda (n init proc trans)
(if (= n 0)
init
(proc (trans n) (new-num-proc (- n 1))))))
(define square-sum
(lambda (n)
(new-num-proc n 0 + square)))
(define cube-sum
(lambda (n)
(new-num-proc n 0 + cube)))
(define other-sum
(lambda (n)
(new-num-proc n 0 + identify)))
現在,你不只可以連加,還可以連加它的平方,也可以連加它的立方。