近年來,Java 引入閉包(Closure)的提案引發熱議。資深作家 Elliotte Rusty Harold 直指核心問題:「for 迴圈到底哪裡可恨?」 這不只是語法糖之爭,更關乎我們如何思考「遍歷」與「運算」的本質。
看看這段再熟悉不過的代碼:
double sum = 0;
for (int i = 0; i < array.length; i++) {
sum += array[i];
}
乍看之下清晰明瞭,但細究其實散落著 30 多個語法符號(分號、括號、比較運算子、索引變數等)。為了簡單的「加總」,我們必須正確寫出迴圈架構、邊界條件、索引遞增——任何一處疏失都可能引發 Bug。
String s = "";
for (int i = 0; i < args.length; i++) {
s += array[i]; // 此處應為 args[i],卻誤寫成 array[i]
}
這類錯誤在編譯期不會報錯,Code Review 也容易漏網,可能潛伏數週才被發現。當迴圈體變大、巢狀加深,風險呈指數級上升。
函數式語言(如 Haskell、Ruby)提供高階函數,將「遍歷機制」內隱,開發者專注於「運算邏輯」:
| 操作類型 | 傳統 for 迴圈 | 高階函數(閉包) |
|---|---|---|
| 歸納 | 手動索引累加 | total = foldl (+) 0 array 或 sum(array) |
| 轉換 | 逐元素修改 | new_array = map (*2) array |
| 過濾 | 雙索引暫存陣列 | odds = filter isOdd nums |
核心差異:for 迴圈將過濾、轉換、歸納三種獨立操作強行合併,且將焦點放在「如何遍歷」這個次要細節。閉包讓我們把「做什麼」傳入演算法,由庫函數處理遍歷細節。
foldl (+) 0 [1,2,3]
= foldl (+) (0+1) [2,3]
= foldl (+) 1 [2,3]
= foldl (+) (1+2) [3]
= foldl (+) 3 [3]
= foldl (+) (3+3) []
= 6
更高階的 `sum`、`product`、`concat` 皆建構於 `fold` 之上,代碼更簡潔、可讀、易維護。
Elliotte 擔心 `3.times {...}` 等閉包寫法在多核心上效率不如 for 迴圈。實則相反:明確語義(map/filter 可並行,fold 需序列)讓編譯器更容易優化。若開發者比編譯器更懂資料特性,甚至可顯式指定並行或序列執行。
for 迴圈並非「惡」,但它迫使我們在低層次操作上耗費心智。擁抱閉包與高階函數,不僅能減少 Bug、提升可讀性,更能訓練我們以資料轉換管線而非控制流程思考問題——這才是現代軟體開發的核心素養。