關於javascript函數式編程中compose的實現

上一篇文章介紹了javascript函數式編程中curry(柯里化)的實現,固然那個柯里化是有限參數的柯里化,等有機會在補上無限參數的那一種柯里化,此次主要說的是javascript函數式編程中另一個很重要的函數composecompose函數的做用就是組合函數的,將函數串聯起來執行,將多個函數組合起來,一個函數的輸出結果是另外一個函數的輸入參數,一旦第一個函數開始執行,就會像多米諾骨牌同樣推導執行了。javascript

簡介

好比有這樣的需求,要輸入一個名字,這個名字有由firstName,lastName組合而成,而後把這個名字所有變成大寫輸出來,好比輸入jacksmith咱們就要打印出來,‘HELLO,JACK SMITH’
咱們考慮用函數組合的方法來解決這個問題,須要兩個函數greeting, toUpperjava

var greeting = (firstName, lastName) => 'hello, ' + firstName + ' ' + lastName
var toUpper = str => str.toUpperCase()
var fn = compose(toUpper, greeting)
console.log(fn('jack', 'smith'))
// ‘HELLO,JACK SMITH’

這就是compose大體的使用,總結下來要注意的有如下幾點編程

  • compose的參數是函數,返回的也是一個函數segmentfault

  • 由於除了第一個函數的接受參數,其餘函數的接受參數都是上一個函數的返回值,因此初始函數的參數是多元的,而其餘函數的接受值是一元數組

  • compsoe函數能夠接受任意的參數,全部的參數都是函數,且執行方向是自右向左的,初始函數必定放到參數的最右面app

知道這三點後,就很容易的分析出上個例子的執行過程了,執行fn('jack', 'smith')的時候,初始函數爲greeting,執行結果做爲參數傳遞給toUpper,再執行toUpper,得出最後的結果,compose的好處我簡單提一下,若是還想再加一個處理函數,不須要修改fn,只須要在執行一個compose,好比咱們再想加一個trim,只須要這樣作函數式編程

var trim = str => str.trim()
var newFn = compose(trim, fn)
console.log(newFn('jack', 'smith'))

就能夠了,能夠看出不論維護和擴展都十分的方便。函數

實現

例子分析完了,本着究其根本的原則,仍是要探究與一下compose究竟是如何實現的,首先解釋介紹一下我是如何實現的,而後再探求一下,javascript函數式編程的兩大類庫,lodash.jsramda.js是如何實現的,其中ramda.js實現的過程很是函數式。this

個人實現

個人思路是,既然函數像多米諾骨牌式的執行,我首先就想到了遞歸,下面就一步一步的實現這個compose,首先,compose返回一個函數,爲了記錄遞歸的執行狀況,還要記錄參數的長度len,還要給返回的函數添加一個名字f1code

var compose = function(...args) {
    var len = args.length
    return function f1() {
        
    }
}

函數體裏面要作的事情就是不斷的執行args中的函數,將上一個函數的執行結果做爲下一個執行函數的輸入參數,須要一個遊標count來記錄args函數列表的執行狀況。

var compose = function(...args) {
    var len = args.length
    var count = len - 1
    var result
    return function f1(...args1) {
        result = args[count].apply(this, args1)
        count--
        return f1.call(null, result)
    }
}

這個就是思路,固然這樣是不行的,沒有退出條件,遞歸的退出條件就是最後一個函數執行完的時候,也就是count0的時候,這時候,有一點要注意,遞歸退出的時候,count遊標必定要回歸初始狀態,最後補充一下代碼

var compose = function(...args) {
        var len = args.length
        var count = len - 1
        var result
        return function f1(...args1) {
            result = args[count].apply(this, args1)
            if (count <= 0) {
                count = len - 1
                return result
            } else {
                count--
                return f1.call(null, result)
            }
        }
    }

這樣就實現了這個compose函數。後來我發現遞歸這個徹底可使用迭代來實現,使用while函數看起來更容易明白,其實lodash.js就是這麼實現的。

lodash實現

lodash的思路同上,不過是用迭代實現的,我就把它的源代碼貼過來看一下

var flow = function(funcs) {
    var length = funcs.length
    var index = length
    while (index--) {
        if (typeof funcs[index] !== 'function') {
            throw new TypeError('Expected a function');
        }
    }
    return function(...args) {
        var index = 0
        var result = length ? funcs[index].apply(this, args) : args[0]
        while (++index < length) {
            result = funcs[index].call(this, result)
        }
        return result
    }
}
var flowRight = function(funcs) {
    return flow(funcs.reverse())
}

能夠看出,lodash的原本實現是從左到右的,但也提供了從右到左flowRight,還多了一層函數的校驗,並且接收的是數組,不是參數序列,並且從這行var result = length ? funcs[index].apply(this, args) : args[0]能夠看出容許數組爲空,能夠看出仍是很是嚴謹的。我寫的就缺乏這種嚴謹的異常處理。

結論

此次主要介紹了函數式編程中的compose函數的原理和實現方法,因爲篇幅緣由,我把打算分析的ramda.js源碼實現放到下一篇來介紹,能夠說ramda.js實現的compose更加函數式,須要單獨好好分析。

相關文章
相關標籤/搜索