JavaScript 函數語言程式設計---柯里化

node快到碗裡來發表於2018-11-16

1、一些術語

1.1 一元函式

只接受一個引數的函式稱為一元函式

const identity = (x) => x
複製程式碼

1.2 二元函式

接受兩個引數的函式

const add = (x,y) => x + y
複製程式碼

1.3 變參函式

接受可變數量引數的函式

// es5
 function variadic(a){
     console.log(a)
     console.log(arguments)
 }
 // es6
 const variadic = (a, ...variadic) => {
     console.log(a)
     console.log(variadic)
 }
複製程式碼

2、什麼是柯里化?

柯里化就是把一個多引數函式轉換為一個巢狀的一元函式的過程。


呵呵,定義看似簡單易懂。但還是讓人感覺鏡中花,水中月。摸不透柯里化的本質。不要急,看官們。讓我們先從簡單的二元函式add柯里化開始。

2.1 二元函式柯里化

const addCurried = x => y => x + y
// 其實就是閉包
function addCurried (x){
    return function (y){
        return x + y
    }
}

addCurried(4)(3)  // 7
複製程式碼

上面是手動地把接受二元函式add轉換為含有巢狀的一元的函式。下面展示如何把該處理過程轉換為一個名為curry的方法

// es5
const curry = (fn){
    return function (firstParam){
        return function (secondParam){
            return fn(firstParam, secondParam)
        }
    }
}
// es6
let curry = fn => firstParam => secondParam => fn(firstParam, secondParam)
複製程式碼

至此,對於函式柯里化,我們應該揭開了TA神祕的面紗。 但是大多數我們寫的函式都是變參的。直接看下面變參函式柯里化

2.2 變參函式柯里化

2.2.1 簡單變參函式柯里化
let curry = (fn) => {
    if(typeof fn !== 'function'){
        throw Error ('引數必須是函式')
    }
    
    return function curriedFn(...args){
        return fn.apply(null,args)
    }
}
複製程式碼

如果我們有個multiply函式

// es 6
const multiply = (x,y,z)=> x*y*z
複製程式碼

我們可以通過以下方式使用2.2中的curry函式

curry(multiply)(1,2,3) // 6
複製程式碼

下面我們看看,curry是如何執行的。 我們在curry函式裡新增了如下程式碼:

return function curriedFn(...args){
    return fn.apply(null,args)
}
複製程式碼

返回函式是一個變參函式,它返回了傳入args並通過apply呼叫函式的結果

fn.apply(null, args)
複製程式碼

通過curry(multiply)(1,2,3),args將會指向[1,2,3],由於我們呼叫了fnapply,等價於:

multiply(1,2,3)
複製程式碼

通過2.2 部分我們實現了變參函式的柯里化,接下來我們實現把多參函式轉換為一元函式的curry函式,就如函式柯里化的定義一樣。

2.2.2 轉換為巢狀的一元函式的curry函式
let curry = (fn)=>{
    if(typeof fn !== 'function'){
        throw Error('引數必須是函式')
    }
    
    return function curriedFn(...args){
        if(args.length < fn.length){
            return function(){
                return curriedFn.apply(null,args.concat([].slice.call(arguments)))
            }
        }
        return fn.apply(null args)
    }
}
複製程式碼

和之前的curry函式相比我們新增了

if(args.length < fn.length){
    return function(){
        return curriedFn.apply(null,args.concat([].slice.call(arguments)))
    }
}
複製程式碼

讓我們逐句理解在這段程式碼中發生了什麼

args.length < fn.length
複製程式碼

這行程式碼是檢查通過...args傳入的引數長度是否小於函式fn引數列表的長度。如果是,就進入if程式碼塊,如果不是,就如之前一樣呼叫整個函式fn。 一旦進入if程式碼塊,就使用apply函式遞迴地呼叫curriedFn函式:

curriedFn.apply(null,args.concat([].slice.call(arguments)))
複製程式碼

此程式碼片段中:

args.concat([].slice.call(arguments))
複製程式碼

非常重要,我們使用concat函式連線每次傳入的引數,並遞迴地呼叫curriedFn。由於我們將所有傳入的引數組合並遞迴呼叫,終將

if(args.length < fn.length)
複製程式碼

條件失敗,即引數列表長度args和函式引數的長度fn.length相等。程式將呼叫整個函式fn

return fn.apply(null args)
複製程式碼

這將產生函式的完整的結果。

好了,函式柯里化就說到這了。 噗。。。感覺寫的不好,大佬們請多多關照

相關文章