[譯] JavaScript 函數語言程式設計指引

zhaofeihao發表於2019-04-01

原文連結 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,這時就會遇到一些問題。

[譯] JavaScript 函數語言程式設計指引

純函式(Pure Functions)

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

[譯] JavaScript 函數語言程式設計指引

頭等函式(First Class Functions)

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

[譯] JavaScript 函數語言程式設計指引

指令式程式設計和函數語言程式設計對比

這裡用下面的例子對兩者進行比較,功能是求得陣列 [1, 2, 3, 4]中的數字之和。

指令式程式設計寫法:

[譯] JavaScript 函數語言程式設計指引
要將上面的程式碼改為函數語言程式設計形式的話,這裡有一個問題需要解決,那就是使用不可變資料結構。但是我們在每次迭代中都給sum賦了不同的值。

為了將程式碼改為函數語言程式設計思想,讓我們來分解一下累加和的計算過程。

首先,我們以某個值作為初始值,在我們的例子中該值為let sum = 0;,接下來,我們從陣列中取出第一項1並將其累加到sum上。這一步我們得到了0 + 1 = 1。然後重複這個步驟,取出2將其累加到sum上,即1 + 2 = 3,這一過程遍歷到陣列尾部為止。

視覺化該過程:

[譯] JavaScript 函數語言程式設計指引
我們可以將該演算法視作兩個單獨的函式,首先我們需要某種方式來進行數字的相加操作:

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

[譯] JavaScript 函數語言程式設計指引
在這個函式中,list是我們想要遍歷的陣列,index作為當前要遍歷的起點位置,如果我們遍歷到達list尾部,或者是給出的list無效,那麼迴圈結束。否則再次呼叫loopindex加 1。試著在return loop(list, index + 1)之前新增console.log(list[index]),我們應該可以看到控制檯輸出了1 2 3 4

為了最終實現求得陣列累加和,我們需要將loopadd函式結合起來,在看下面的例子時要回憶我們上面提到的演算法:

[譯] JavaScript 函數語言程式設計指引
這裡改變了傳入loop函式中的引數,增加了accu,用來儲存list的累加和。我們直接用add函式計算accu與與list中當前項的和。如果我們console.log(loop(list));會發現控制檯會輸出結果 10。

現在,不如我們更進一步吧。要是我們不想求陣列累加和了,改為將它們相乘呢?那現在我們要複製一份loop的程式碼,把add函式改成其他東西(可能是multiply?)?太麻煩了吧。還記得頭等函式嗎,我們要利用這種思想將我們的函式變的更為通用。

[譯] JavaScript 函數語言程式設計指引

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

[譯] JavaScript 函數語言程式設計指引

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

[譯] JavaScript 函數語言程式設計指引

結語

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

相關文章