簡介:歡迎來到函數式程式設計的世界!
在你以往的編碼經驗中,你很可能習慣使用指令式程式設計(例如 Python 或 Java),即你需要一步步告訴電腦「該如何」執行任務(例如:「開始一個迴圈、將數值加到變數中、修改該數值」)。
撰寫函數式程式則是一種截然不同的思維模式。你不再是給電腦一份會改變記憶體數值的「代辦事項清單」,而是將程式視為一系列數學上的轉換(transformations)。這種方式更簡潔、更容易測試,且在處理大數據時非常強大!如果一開始覺得有點「反直覺」也不用擔心——一旦你掌握了竅門,就會發現它是多麼的優雅。
1. 高階函數 (Higher-Order Functions, HOFs)
函數式程式設計的秘密武器就是高階函數。通常我們認為函數的輸入是資料(如整數或字串),但在函數式程式設計中,函數是一等公民(first-class objects),這意味著它們可以像其他任何數值一樣被處理。
高階函數是指具備以下至少其中一項特徵的函數:
- 將另一個函數作為參數。
- 將函數作為運算結果返回。
「工具箱」比喻:
想像一個普通函數是一把鐵鎚。你用它來處理一根釘子(資料)。
而高階函數就像一隻機械手臂。你可以給機械手臂一把鐵鎚,並指示它去處理房間裡的每一根釘子。機械手臂就是那個高階函數,因為它把「工具」當作輸入。
重點總結:高階函數讓我們能夠透過將不同的行為「插入」到單一的主要函數中,從而編寫出靈活性極高的程式碼。
2. 「三大」高階函數
AQA 要求你掌握三個專門用於處理列表的高階函數,分別是 map(映射)、filter(篩選)和 reduce(歸納,有時也稱為 fold)。
A. Map (映射)
map 函數會將指定的函數應用於列表中的每一項,並返回一個包含結果的新列表。
範例: 如果你擁有一個數字列表 \( [1, 2, 3] \),並對其執行 map 「翻倍」函數,你就會得到 \( [2, 4, 6] \)。
記憶技巧:將 Map 想像成一根魔法棒。你在整排南瓜上揮舞,它們就全都變成了馬車。項目的數量不變,但它們的形式改變了。
B. Filter (篩選)
filter 函數會檢查列表中的每一項是否符合特定條件(布林值測試),並返回一個新列表,其中只包含通過測試的項目。
範例: 如果你擁有列表 \( [10, 55, 2, 90] \),並透過 filter 篩選出「大於 50 的數字」,你就會得到 \( [55, 90] \)。
記憶技巧:將 Filter 想像成夜店門口的保安。他們檢查「名單」上的每個人,只放行那些符合「條件」(例如:在嘉賓名單上)的人進場。
C. Reduce (或 Fold,歸納)
reduce 函數會將列表「壓縮」成一個單一值。它透過重複將組合函數應用於列表項目來達成此目的。
範例: 如果你擁有列表 \( [1, 2, 3, 4] \),並使用「加法」函數執行 reduce,過程會是:\( 1+2=3 \),接著 \( 3+3=6 \),最後 \( 6+4=10 \)。最終結果為 \( 10 \)。
記憶技巧:將 Reduce 想像成一個雪球。當它滾過列表時,它會捲入每一項並將其加入自身,直到最後你得到一個巨大的雪球。
快速回顧:
- Map:項目數量相同,數值改變。
- Filter:項目數量減少,數值不變。
- Reduce:最終結果為單一數值。
3. 函數式語言中的列表處理
在函數式程式設計(如 Haskell)中,列表的結構與你可能習慣的陣列不同。一個列表基本上由頭部 (Head) 和 尾部 (Tail) 組成。
- Head:列表中的第一個元素。
- Tail:除頭部以外,列表中的其餘所有內容。
- 空列表:表示為 []。
「火車」比喻:
把列表想像成一列火車。Head 是火車頭。Tail 是掛在火車頭後面的整列火車廂。如果你把火車頭拆下來,剩下的車廂就會變成一列(稍微短一點的)新火車!
常見列表操作:
- Return Head:取得第一個項目(例如:\( [5, 8, 2] \) 的 Head 是 \( 5 \))。
- Return Tail:取得剩餘列表(例如:\( [5, 8, 2] \) 的 Tail 是 \( [8, 2] \))。
- Prepend:在前端加入項目(例如:將 \( 10 \) 放到 \( [2, 3] \) 的前端,得到 \( [10, 2, 3] \))。在 Haskell 中通常寫作 10:[2, 3]。
- Append:在末端加入項目。
- IsEmpty:檢查列表是否為 []。這對於停止遞迴 (recursion) 至關重要!
你知道嗎?函數式程式通常使用遞迴來代替迴圈(for/while)。要處理一個列表,函數會處理 Head,然後呼叫自身來處理 Tail,不斷重複直到遇到空列表為止。
4. 重要語言與概念
AQA 提到了一些支援函數式程式設計的語言。你不必精通所有語言,但應該認識它們:
- 純函數式:Haskell, Lisp, Scheme, Standard ML。
- 多重典範(支援函數式特性):Python, C#, Java 8+, VB.NET。
關鍵點:不可變性 (Immutability)
在函數式程式設計中,資料是不可變的。這意味著一旦列表被建立,你就不能「修改」它。當你使用 map 時,你並沒有改變原始列表,而是建立了一個包含轉換後數值的全新列表。這能防止「副作用 (side effects)」,讓你的程式碼更具可預測性!
總結:撰寫函數式程式涉及使用高階函數(map, filter, reduce)來處理不可變列表,並透過將列表拆解為頭部與尾部來進行運算。
5. 應避免的常見錯誤
1. 混淆 Map 與 Filter:學生常在想要移除項目時使用 map。請記住:Map 無法改變列表的長度;只有 Filter 可以做到。
2. 忘記 Tail 也是一個列表:Head 是單一項目(一個整數、一個字元),但 Tail 永遠是一個列表,即使列表內只剩下一個項目!
3. 用「指令式」思維思考:不要試圖在函數式程式中使用計數器(例如 \( i = i + 1 \))。請改用「三大」高階函數或遞迴。
如果一開始覺得很棘手,別擔心!函數式程式設計就像學騎腳踏車一樣——剛開始搖搖晃晃,直到你的大腦突然找到平衡點。練習在紙上寫出 map(double, [1, 2, 3]) 的執行過程,這一切很快就會變得自然。