初見函數語言程式設計

Raaabbit發表於2019-05-27

在學習 JS 的過程中時常會聽到一個名次——“函數語言程式設計”,那麼究竟什麼是函數語言程式設計,函數語言程式設計又有什麼優點,這就在這篇部落格進行一個簡單的總結吧~

主要內容:

  • 函數語言程式設計的概念
  • 函數語言程式設計的優點與示例

什麼是函數語言程式設計

首先,我們放下程式設計的概念,我們來看函式。

函式的概念來自於數學,數學中的函式 f(x) = y 有一個非常重要的特點對於一個給定的 x,有唯一的 y 與其對應(這就是為什麼橢圓曲線不是函式)

然而在程式設計中,函式並不具有這個特點,舉個栗子:

let val = 1
function add(x){
    return x + val
}
console.log(add(1)) // 2
val += 1
console.log(add(1)) // 3
複製程式碼

可以看到程式設計中的函式在引數相同的情況下,允許有不同的返回值——只要依賴於函式外部的量

那麼當函式依賴於外部的變數(常量不會有這樣的問題),並且函式在多處呼叫,就有可能出現 bug,函式的呼叫結果可能會和預期結果相去甚遠

函數語言程式設計就要求我們規避這樣的情況,讓所有函式對於相同的輸入的返回值相同,這樣的特性就叫做引用透明性,這就是函數語言程式設計的核心特性!

一個符合引用透明性的函式的栗子:

function id(x){ return x; }
複製程式碼

函數語言程式設計帶來的優點

在上文中提到過,程式語言中的函式大多是不滿足數學中的函式的概念的,so 我們將滿足數學函式條件的函式稱為“純函式”

函數語言程式設計的優點大多都來自於純函式

可測試性

除了測試人員進行的全方位測試外,我們在開發過程中往往要對自己寫的程式碼進行模組測試

在開發中,我受非純函式迫害已久,由於它依賴了外部變數(比如儲存在 localStorage 中的資料)我不得不三番五次去檢查這些外部變數是否在某個過程中被改變甚至是刪除

let val = 1
function add(x){
    return x + val
}
console.log(add(1)) // 2
// 在未知因素影響下 val被改變
console.log(add(1)) // 預期結果 2,實際輸出 emmmmm
複製程式碼

如果我沒有注意外部依賴而是一頭扎進函式邏輯裡,可能永遠都找不到這個bug

程式碼的併發性

雖然我們都知道 JS 是一門單執行緒語言(關於JS的執行可以參見->技術總結——JS的執行順序),但是我們為了提高前端的效能可能會通過 WebWorker 來併發執行多個任務,或者在 Node 環境下 JS 併發執行函式

這個時候就是對非純函式的一個很大的考驗:

let global = "全域性變數"
let func1 = ()=>{
    global = "全域性變數被改變了"
    // 一些邏輯
}
let func2 = ()=>{
    if(global = "全域性變數"){
        return true
    }
}
複製程式碼

上面的兩個函式都依賴於外部的global,當它們併發執行,func1就會對func2產生影響,如果將它們變為純函式就不會有這樣的問題:

let global = "全域性變數"
let func1 = (x)=>{
    x = "全域性變數被改變了"
    // 一些邏輯
}
let func2 = (x)=>{
    if(x = "全域性變數"){
        return true
    }
}
複製程式碼

函式執行的快取

當我們的函式都是純函式,而我們又會多次呼叫函式,我們就可以對函式物件進行一個快取

比如我們需要大量計算數字的4次方,我們可以建立一個對映表用來快取函式的執行結果:

// 對映表
let fourTimesTable = {};
let fourTimes = (x){
    return x*x*x*x
}
// 檢查表中是否有 2 的四次方,如果有就返回,如果沒有就執行函式避免運算
fourTimesTable.hasOwnProperty(2)?
    fourTimesTable[2]:
    fourTimesTable[2] = fourTimes(2)
複製程式碼

管道與組合

管道過濾器是一種很經典的設計模式,我們可以將這種模式和函數語言程式設計結合起來

在管道和過濾器軟體體系結構中,每個模組都有一組輸入和一組輸出。每個模組從它的輸入端接收輸入資料流,在其內部經過處理後,按照標準的順序,將結果資料流送到輸出端,以達到傳遞一組完整的計算結果例項的目的。

在這種結構中,各模組之間的聯結器充當了資料流的導管,將一個過濾器的輸出傳到下一個過濾器的輸入端。所以,這種聯結器稱為“管道”。

我們也可以將這樣的私用運用在函數語言程式設計中,將一個個函式作為過濾器,通過函式的組合,形成一條資料處理的通路:

// 引數累加
function add(...args){
    let result = args.reduce((prev, cur, index, arr)=> {
        return prev + cur;
    })
    return result
}
// 引數累乘
function times(...args){
    let result = args.reduce((prev, cur, index, arr)=> {
        return prev * cur;
    })
    return result
}

let arr1=[1,3,6],arr2=[2,5,21],arr3=[3,7,8,27,4]
// 三組資料,要求組內累乘,然後結果累加
add(times(...arr1),times(...arr2),times(...arr3))
// 三組資料,要求組內累加,然後結果累乘
times(add(...arr1),add(...arr2),add(...arr3))
複製程式碼

對函數語言程式設計的初探暫止於此,進一步學習後再做總結~

本文同步釋出於我的個人部落格CSDN掘金

如果有什麼問題,意見,建議歡迎評論;如果覺得我寫的不錯,那就點個贊吧~

相關文章