歡迎來到函數式程式設計的世界!
在你至今接觸過的大多數程式設計中,你可能都在使用命令式範式 (Imperative paradigm)——即一步步告訴電腦「如何」去做某事(就像食譜一樣)。在本章中,我們將探索函數式程式設計範式 (Functional programming paradigm)。與其編寫一連串的指令,我們將程式視為一系列數學轉換的過程。
如果起初覺得這聽起來有點「數學味」,別擔心!我們將透過你已經熟悉的簡單概念,例如自動販賣機和樂高積木,來深入淺出地講解這些概念。
4.12.1.1 函數類型 (Function Type)
在函數式程式設計中,函數 (Function) 是一條將一組輸入映射至一組輸出的規則。我們使用以下符號來表示:
\( f: A \rightarrow B \)
這個符號是什麼意思?
- \( f \):這只是函數的名稱。
- \( A \):這是定義域 (Domain)。它是所有可能「輸入」值的集合。
- \( B \):這是對應域 (Co-domain)。它是從中選擇「輸出」值的集合。
規則:對於定義域中的每一個輸入,函數都會在對應域中指派唯一一個輸出。但請注意,對應域中的值並不一定全部都要被用到!
例子:想像一個告知你字母在字母表中位置的函數。
定義域:{a, b, c... z}
對應域:{0, 1, 2... 25}
如果你輸入 'a',函數會將其映射到 0。如果你輸入 'b',則映射到 1。
快速回顧:定義域 vs. 對應域
試想一台自動販賣機:
定義域是你按下的按鈕(A1, B2 等)。
對應域是機器裡的所有零食。即使機器剛好賣完了薯片,但「薯片」仍然屬於對應域,因為它們「可能」作為一種輸出。
重點總結:定義域是你的數據來源;對應域則是可能的結果「池」。
4.12.1.2 一等物件 (First-class Objects)
在函數式程式設計中,函數是一等物件 (First-class objects)。這是一個比較正式的說法,意思是函數被視為與其他數據類型(如整數或字串)完全相同。
如果某物是一等物件,它可以:
- 出現在表達式 (Expressions) 中。
- 被指派給變數 (Assigned to a variable)。
- 作為引數 (Argument) 傳遞給另一個函數。
- 作為函數呼叫的結果被回傳 (Returned)。
你知道嗎?在 Python 或 Haskell 等語言中,你可以直接將變數設為一個函數,例如:my_function = print。現在,my_function("Hello") 的運作方式就和 print 指令完全一樣!
重點總結:一等物件是程式語言中的「VIP」公民——它可以出現在任何地方,並像普通數據一樣被使用。
4.12.1.3 函數應用 (Function Application)
函數應用就是將輸入(引數)提供給函數的過程。
例子:如果你有一個名為 add 的函數,那麼 add(3, 4) 就是該函數對引數 3 和 4 的應用。
「單一引數」的秘密
儘管我們可能會說像 add(x, y) 這樣的函數接受兩個引數,但在函數式程式設計中,技術上將其視為僅接受一個引數:一對數字。
在數學上,我們這樣寫: \( f: \text{integer} \times \text{integer} \rightarrow \text{integer} \)
符號 \( \times \) 代表笛卡兒積 (Cartesian product),這只是一種稱呼「一對整數」的華麗說法。
重點總結:應用函數意味著用真實數據來「觸發」它。
4.12.1.4 偏函數應用 (Partial Function Application)
這部分非常酷!由於函數是一等物件,我們可以只給函數「部分」引數。這會回傳一個新的、特化的函數,它在等待剩餘的引數。
類比:想像一台咖啡機。
1. 你放入「咖啡膠囊」作為引數。
2. 機器還不會給你飲料;它現在變成了一台特化機器,正在等待「水」這個引數。
3. 一旦你加入水,流程就完成了。
標記法
讓我們看看 add 函數:
通常情況: add(x, y)
在偏函數應用中: add: integer \( \rightarrow \) (integer \( \rightarrow \) integer)
如果我們呼叫 add 4,它會回傳一個新的函數,該函數會將你給它的任何數字加上 4。如果你隨後給這個新函數一個 6,最終結果就是 10。
避免常見錯誤:
不要將偏函數應用與普通的函數呼叫搞混。在普通呼叫中,你想要一個最終答案(如 10)。在偏函數應用中,你是刻意建立一個新的、更簡單的函數以供稍後使用。
重點總結:偏函數應用將一個需要多個引數的函數,轉化為一系列每次僅接受一個引數的函數。
4.12.1.5 函數複合 (Composition of Functions)
函數複合 (Functional composition) 是將兩個函數結合起來建立一個全新函數的過程。你將第一個函數的輸出作為第二個函數的輸入。
如果我們有兩個函數:
\( f: A \rightarrow B \)
\( g: B \rightarrow C \)
我們可以將它們組合成一個新函數:\( g \circ f \)
重要:在符號 \( g \circ f \) 中,右邊的函數(\( f \))先執行!其結果隨後會傳遞給左邊的函數(\( g \))。
現實生活中的例子:
想像兩個函數:
1. f(x):將數字加 2。
2. g(x):將數字取立方 (\( x^3 \))。
複合函數 \( g \circ f \) 就是: \( (x + 2)^3 \)。
如果你輸入 1:先加 2(結果為 3),然後取立方(最終結果為 27)。
記憶法: 「Fog」法則
當你看到 \( g \circ f \) 時,可以把它倒著讀,或者將其想成 "g of f"。這就像一條生產線——數據從右邊的函數流入左邊的函數。
重點總結:複合就像將樂高積木拼在一起。一個「積木」的輸出必須完美契合下一個積木的輸入,才能構建出更大的東西。
本章總結 - 快速回顧
- 函數類型:由其定義域(輸入)和對應域(可能的輸出)定義。
- 一等物件:函數可以像任何其他變數一樣被處理(傳遞、回傳等)。
- 函數應用:使用帶有特定輸入的函數。
- 偏函數應用:僅提供部分引數以建立一個新的、特化的函數。
- 複合:將兩個函數(\( f \) 和 \( g \))結合,使前者的輸出成為後者的輸入(\( g \circ f \))。