JavaScript 函數語言程式設計中的 curry 實現

dongzhe3917875發表於2017-02-20

最近在學習javascript函數語言程式設計,對其中大名鼎鼎的curry十分感興趣,curry函式可以接受一個函式,我們暫且稱之為原始函式,返回的也是一個函式,柯里化函式,這個返回的柯里化函式功能十分強大,他在執行的過程中,不斷的返回一個貯存了傳入引數的函式,直到觸發了原始函式執行的條件。這麼說比較概括,那麼就舉個例子來說明一下:

原始函式:

var add = (x, y) => x + y

柯里化函式:

 var curryAdd = curry(add)

這個add需要兩個引數,但是我們的curryAdd執行可以傳入更少的引數,當傳入的引數少於add需要的引數的時候,add函式並不會執行,curryAdd就會將這個引數記下來,並且返回另外一個函式,這個函式可以繼續執行傳入引數,我們會有一個變數專門記錄傳入引數的情況,如果傳入引數的總數等於add需要引數的總數,我們就啟用了原始引數執行,就會返回我們想要的結果。

// 此時只傳入了一個引數 根據判斷返回的是一個函式
    var add2 = curryAdd(2)
    // add2 = function(...) {}
// 此時累計傳入了兩個引數 等於了add需要引數的總和 所以返回的是一個結果
    // 相當於執行了add(2)(3)
    var result = add2(3)
    // result = 5

還是很不錯的是吧,好吧,我們的目的是為了寫出這個神奇curry函式,而且還要一行寫出來,不要著急,先分析一下怎麼去寫,然後再一步步的優化。

那根據上面的描述,我們看一下curry函式需要什麼,首先需要一個變數,用來存下來原始函式的引數個數,我們知道function有一個屬性為length,對就是它,我們用limit存下來

    var curry = function(fn) {
         var limit = fn.length
         ...
    }

curry函式要返回一個函式, 這個函式是要執行的,那麼問題就是,我們要判斷這個函式的執行是否啟用了原始函式的執行,問題就出現在傳入的引數上面。返回函式還是結果?這的確是一個問題,我們先寫返回結果的情況,當傳入的引數等於原始函式需要的引數時,我們執行原始函式fn

    var curry = function(fn) {
         var limit = fn.length
         return function (...args) {
             if (args.length >= limit) {
                 return fn.apply(null, args)
             }
         }
    }

否則呢 我們就要返回一個貯存了引數的函式,這裡有兩點,一是引數的傳入歷史我們要記錄下來,二是這個返回的函式需要做些什麼

    var curry = function(fn) {
         var limit = fn.length
         return function (...args) {
             if (args.length >= limit) {
                 return fn.apply(null, args)
             } else {
                 return function(...args2) {

                 }
             }
         }
    }

看出來了吧 我們只需要把返回函式執行的引數累加起來就達到了記錄引數傳入情況的目的,於是我們想到了concat 對 args.concat(args2), 依次類推,我們返回的函式要做的就是重複做上面的事情,也就是引數為args的函式要做的事情,所以他需要一個名字,不然我們沒法執行,我們叫它judgeCurry

所以正如我們所說的,要麼返回一個函式,要麼執行原始函式。

    var curry = function(fn) {
         var limit = fn.length
         return function judgeCurry (...args) {
             if (args.length >= limit) {
                 return fn.apply(null, args)
             } else {
                 return function(...args2) {
                     return judgeCurry.apply(null, args.concat(args2))                                     
                 }
             }
         }
    }

我們終於寫完了這個神奇的curry函式,它真的很強大,配合compose,那真是一個字,爽。

我們的目的還是一行把上面那個函式寫出來,一行寫?怎麼寫?對了,用ES6啊,於是一番折騰

var currySingle = fn => judgeCurry = (...args) => args.length >= fn.length ? fn.apply(null, args) : (...args2) => judgeCurry.apply(null, args.concat(args2))

好,我們看一下哪有問題,對了,就是我們為了不用limit引數,用了就得賦值,賦值就不能一行搞定了,就會變成這樣

    var currySingle = fn => {
        var limit = fn.length
        var judgeCurry = null
        return judgeCurry = (...args) => args.length >= limit ? fn.apply(null, args) : (...args2) => judgeCurry.apply(null, args.concat(args2))
    }

需要判斷引數的時候不斷的對fn.length求值,但是fn.length的值是確定的,我們不想每次都求值,但又不想用limit怎麼辦,有什麼辦法呢?你一定想到了,立即執行函式!!

var currySingle = fn => ((limit) => judgeCurry = (...args) => args.length >= limit ? fn.apply(null, args) : (...args2) => judgeCurry.apply(null, args.concat(args2)))(fn.length)

不得不感嘆javascript的神奇,終於,我們就一行將這個神奇的curry寫出來了。

相關文章