iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 4
1
Software Development

每天 Racket 3 分鐘系列 第 7

(display "λf.(λx.f (x x)) (λx.f (x x)) — day-06 — Racket 的 Function 與 Lambda — 1")

1. 從圖靈的老師談起

他是圖靈在普林斯頓時的老師,是當時一位頂尖的數學家與計算理論專家。當一個文化進入另一個時,首先需要做的是將原有的語彙翻譯過去。然而在台灣的電腦科學界,我找不到這個人名字的翻譯。他叫做 Alonzo Church,在對岸的文章中稱他為:阿隆佐,邱奇,因此,我文章以 Church 來稱呼他。

在那個時代裡,還沒有現代的電腦,那時人類還在探索用機器進行數學計算的可能性。而 Church 正是投下第一彈的人,提出了將數學函式抽象化的原則:lambda calculus。

從這時候開始,人類開始討論的不是函式怎麼算出來,而是函式的抽象變化與應用。lambda calculus 的概念也形塑了最早期的程式語言模型。

2. 來看看最有現代感的 JS

回到現代,這個時代最多人用的語言,我想應該是 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 一個 lambdaid 的過程。因此,你若在 Chez Scheme (最好的 Scheme 實作) 的 source code 看看它裡頭使用的函式宣告,幾乎清一色是用 lambda 的宣告風格。

然而,第二種宣告方式,其實是更多人慣用的方式,用 () 將函式的名稱與參數包起來,接下來就是接函式定義的本體,不使用 lambda。我猜應該是這樣寫,打的字比較少,所以更多人愛用吧!

現在,你會了基本的函式宣告,也看到上面示範的 square 了,你試著來宣告 cube (立方)吧。

3. 回到 Scheme 規格書

很多語言其實是沒有規格書的,但 Scheme 不只有,而且言簡意賅,頁數極少。在 Scheme 規格書裡,第一章介紹整個 Scheme 語言的概覽,第一章的第一小節介紹 Scheme 的資料型態,第一小節的最後一項,介紹函式(原文稱 Procedure),他是這麼說的:

Procedures Procedures are values in Scheme. [1]

是的,在 Scheme 與 Racket 裡面,函式不是像值,函式就是值。因為它是值,在語義上,可以進行多種變化與應用。

4. 你的語言能不能這麼做

剛剛上頭,我留下一個練習,要各位寫一個 cube 的函式。我們現在來想想,怎樣在 Racket 裡頭做連加?假設我有一組連續數字:0 ~ n,我要把它連加起來,不使用公式解,使用 Functional Programming Language 標準的遞迴解,應該怎麼寫呢?

(define sum
  (lambda (n)
    (if (= n 0)
        0
        (+ n (sum (- n 1))))))

這裡引入一個過幾天會談到的 ifif 也是一個函式,它有三個關鍵點,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))))))

然後把 sumfactor 改為這樣:

(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

的緣故。好,最後一個變化,使用我們前面的 squarecube,並加上一個 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)))

現在,你不只可以連加,還可以連加它的平方,也可以連加它的立方。


上一篇
(if day-05 "來杯咖啡談是非 — Racket 的其他型態" (void))
下一篇
(list 'day-07 "組織你的資料 — Racket 基礎資料結構:Pair、List 與 Vector")
系列文
每天 Racket 3 分鐘17
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言