從重複代碼說起#

Joel 以一個簡單的 JavaScript 範例開場:兩段幾乎相同的代碼,唯一的差別是一塊與「Spaghetti」有關,另一塊與「Chocolate Mousse」有關。用函數消除重複的道理大家都懂——更容易維護、可讀性更好、更容易重複利用。

函數作為參數的威力#

當你發現兩段代碼除了呼叫不同的函數(如 PutInPot vs BoomBoom)外完全一樣時,你需要一種方法能將函數作為參數傳入另一個函數

function Cook(i1, i2, f) {
  alert("get the " + i1);
  f(i1);
  f(i2);
}
Cook("lobster", "water", PutInPot);
Cook("chicken", "coconut", BoomBoom);

這種能力叫做 first-class function(第一類函數)。你的程式語言如果無法將函數作為參數傳遞,你就無法享受現代程式設計環境帶來的全部好處。

匿名函數#

更進一步,如果你還沒有定義 PutInPotBoomBoom,可以在呼叫處直接用 anonymous function(匿名函數)建立:

Cook("lobster", "water", function (x) {
  alert("pot " + x);
});

從 map 到 reduce#

map 函數#

  • 對陣列的每一個元素進行某種操作是非常常見的模式
  • 寫一個 map(fn, a) 函數,就可以用 map(function(x){return x*2;}, a) 取代手動迴圈

reduce 函數#

  • sumjoin 看起來很像,都是將陣列的元素結合成一個值
  • 抽象為通用的 reduce(fn, a, init) 函數

許多老舊的程式語言無法實現這些功能。C 語言雖有函數指標但使用不便;Java 要求你為每個 functor 建立一個只有一個方法的物件。如果你的語言要用到 functor,你最好去查一下能不能退貨。

通往 MapReduce 之路#

Joel 指出:map 函數處理陣列時,元素的遍歷順序其實無關緊要——可以從頭到尾,也可以從尾到頭,甚至可以分配到多個 CPU 並行處理

  • 想像有幾十萬台伺服器,上面有超級龐大的資料集
  • 你可以在這些伺服器上運行 map 函數,每台負責一小部分
  • 你寫出的搜尋引擎代碼,只要呼叫 map 加上一個字串搜尋器就行

這正是 Google 的 MapReduce 技術背後的原理。MapReduce 這兩個術語來自 Lisp 語言和函數式程式設計。如果你不懂函數式程式設計,你就無法發明出 MapReduce。純粹的函數式程式設計沒有副作用,因此可以直接用於並行計算

結論#

那些具備「第一類函數」功能的程式語言,能讓你更容易完成進一步抽象代碼的任務。你的代碼體積更小、更緊湊、更容易重複利用、更方便擴展。Google 的許多應用程式都使用了 MapReduce 技術,一旦有人對底層的並行計算程式進行優化或消除 bug,所有應用程式都會受益。