[譯] JavaScript 函數語言程式設計指引
原文連結 Introduction to Functional Programming
本文旨在對比指令式程式設計與函數語言程式設計兩種不同的解決問題的方式。目的並不是專門教大家函數語言程式設計,而是介紹給大家一種區別於傳統方法(迴圈、變換等)的不同的思考方式。在日後遇到問題時能從不同的角度去思考,同時給自己的技能樹新增更多的工具。
函數語言程式設計的基礎可以分為如下三個要點來闡述:
- 不可變資料結構(Immutable Data Structures)
- 純函式(Pure Functions)
- 頭等函式(First Class Functions)
讓我們分別看一下每個要點吧:
不可變資料結構(Immutable Data Structures)
我們在使用像 JavaScript 這樣的程式語言時,我們可以這樣給變數進行賦值 let myVariable = 5;
,但是不存在任何一種機制來阻止我們在後期繼續對該變數進行賦值操作,比如 myVariable = "Now I'm a string."
。這樣的操作實際上很危險,如果有其他函式依賴 myVariable
這個 number
型別變數,或者是某個非同步函式同時也要用到 myVariable
,這時就會遇到一些問題。

純函式(Pure Functions)
純函式是無副作用的。啥叫無副作用? 首先,如果一個函式的輸出僅僅依賴於其輸入,那麼該函式就被認為是一個純函式。如果我們的函式拿到輸入後執行了資料庫的更新操作,然後返回了一個值,這樣的操作就稱之為具有副作用,即更新了資料庫。也就是說多次呼叫同一個函式不總是得到相同的結果(記憶體不足、資料庫被鎖等情況)。使用純函式對於我們書寫少 bug、易測試的程式碼很有幫助。

頭等函式(First Class Functions)
「頭等」這個詞出現在這裡可能看起來比較陌生,但是其意思是指函式可以被用做引數或者可以像其他資料型別一樣使用。比如字串型別、整型和浮點型等。支援頭等函式的程式語言允許將函式作為引數傳給其他函式,可以把這種方式看做依賴注入。

指令式程式設計和函數語言程式設計對比
這裡用下面的例子對兩者進行比較,功能是求得陣列 [1, 2, 3, 4]
中的數字之和。
指令式程式設計寫法:

sum
賦了不同的值。
為了將程式碼改為函數語言程式設計思想,讓我們來分解一下累加和的計算過程。
首先,我們以某個值作為初始值,在我們的例子中該值為 let sum = 0;
,接下來,我們從陣列中取出第一項 1
並將其累加到 sum
上。這一步我們得到了 0 + 1 = 1
。然後重複這個步驟,取出 2
將其累加到 sum
上,即 1 + 2 = 3
,這一過程遍歷到陣列尾部為止。
視覺化該過程:

我們可以將該演算法視作兩個單獨的函式,首先我們需要某種方式來進行數字的相加操作:

接下來我們需要以某種方式來迴圈遍歷陣列,由於大多數函數語言程式設計通常都使用遞迴來替代迴圈,那我們就建立一個遞迴函式來遍歷我們的陣列。來看一下該函式可能的樣子:

list
是我們想要遍歷的陣列,
index
作為當前要遍歷的起點位置,如果我們遍歷到達
list
尾部,或者是給出的
list
無效,那麼迴圈結束。否則再次呼叫
loop
,
index
加 1。試著在
return loop(list, index + 1)
之前新增
console.log(list[index])
,我們應該可以看到控制檯輸出了
1 2 3 4
。
為了最終實現求得陣列累加和,我們需要將 loop
和 add
函式結合起來,在看下面的例子時要回憶我們上面提到的演算法:

loop
函式中的引數,增加了
accu
,用來儲存
list
的累加和。我們直接用
add
函式計算
accu
與與
list
中當前項的和。如果我們
console.log(loop(list));
會發現控制檯會輸出結果 10。
現在,不如我們更進一步吧。要是我們不想求陣列累加和了,改為將它們相乘呢?那現在我們要複製一份 loop
的程式碼,把 add
函式改成其他東西(可能是 multiply
?)?太麻煩了吧。還記得頭等函式嗎,我們要利用這種思想將我們的函式變的更為通用。

上面例子中唯一不同之處在於我們給 loop
函式新增了一個引數,來接收一個函式。這回我們不傳 add
給它了,取而代之的是傳一個函式進來,呼叫該函式獲取最終結果。這樣就能輕鬆的實現針對 list
的 add
, multiply
, subtract
等一系列操作了。

我們不再只是簡單的遍歷陣列了,而是將陣列像紙一樣折起來,直到我們得到一個結果。在 JavaScript 中,我們把這種函式稱之為 reduce
。

結語
我們對函數語言程式設計進行了一些基礎的概覽,同時看到針對同一問題的不同拆解方式給與我們不同的解法。 reduce
可以說是其他類似 map
和 filter
這樣的操作的基礎。