ramda.js的compose源碼解析

前言

上一篇文章介紹了javascript中的compose函數的實現,我是用了遞歸的思想去讓函數依次執行,lodash中是用了迭代的思想依次執行函數,但實現了之後我仍是以爲有些彆扭,仔細想一想,咱們實現的是一個函數式編程用到的函數,可是實現的方法仍是太命令式了,函數仍是命令式的執行,通俗點說,仍是太把函數當成函數了,在個人理解中,函數和普通變量沒什麼區別,只是執行的方法不同,一旦賦予了函數這個執行的屬性,咱們就能夠徹底將函數當成普通變量去對待。javascript

函數和普通變量沒什麼區別,只是須要偶爾執行一下java

實現

1.函數世界的加號

舉個例子git

1 + 2 = 3
'a' + 'b' = 'ab'
func1 '+' func2 -> func3

前兩個例子就是普通變量的操做,最後一個例子是函數的操做,本質上看來,沒有任何區別,兩個函數做用的結果就是生成一個函數,只不過在函數的世界裏,這個加號的意義就是如何變換生成一個新的函數,回到compose來,在compose中,加號的意義就是把一個函數的執行結果當成下一個函數的輸入,最後在生成一個函數,就像下面這樣github

var fn = (func1, func2) => (...args) => func2.call(this, func1.apply(this, args))

在這個例子裏面,func1的執行結果就是func2的參數,而且生成了一個新的函數fn,咱們給這個fn傳遞參數,它就會做爲func1的參數來啓動執行,最後獲得了函數依次執行的效果,這就是最簡單的compose,這個函數就是ramda.js實現compsoe須要的第一個函數_pipe編程

var _pipe = (f, g) => (...args) => g.call(this, f.apply(this, args))

_pipe就定義了compose中所謂加號的意義了。segmentfault

2.'不同的'reduce

在這裏提到了reduce,是否是有一點感受,reduce的做用就是讓一個數組不斷的執行下去,因此確定能和我們這個compose有點聯繫,先舉個reduce最經常使用的例子,求數組的和數組

var a = [1,2,3,4,5]
a.reduce((x, y) => x + y, 0)

這個就是不斷的將兩個數求和,生成一個新的數,再去和下一個數求和,最後獲得15,下面想一下,若是把數字換成函數會怎麼樣,兩個函數結合生成一個新的函數,這個結合法則就使用上面的_pipe,這個新的函數再去結合下一個函數,直到最後一個函數執行完,咱們獲得的仍是函數,咱們前面說了,函數知識偶爾須要執行一下,這個函數的生成和執行過程是反向遞歸的過程。利用這個思想,就能夠寥寥幾行(甚至只須要一行)就寫出來這個很是函數式的compose數據結構

var reverse = arr => arr.reverse()
var _pipe = (f, g) => (...args) => g.call(this, f.apply(this, args));
var compose = (...args) => reverse(args).reduce(_pipe, args.shift())

舉個例子驗證一下,咱們把首個函數作多元處理,再upperCase,再repeatapp

var classyGreeting = (firstName, lastName) => "The name's " + lastName + ", " + firstName + " " + lastName
var toUpper = str => str.toUpperCase()
var repeat = str => str.repeat(2)
var result = compose(repeat, toUpper, classyGreeting)('dong', 'zhe')
// THE NAME'S ZHE, DONG ZHETHE NAME'S ZHE, DONG ZHE

我在這裏把函數生成過程分析一下函數式編程

首先咱們用_pipe組合classyGreetingtoUpper

f1 = _pipe(classyGreeting, toUpper)
f1 = (...args) => toUpper.call(this, classyGreeting.apply(this, args))

_pipe繼續結合f1, repeat

f2 = _pipe(f1, repeat)
f2 = (...args) => repeat.call(this, f1.apply(this, args))

函數的執行過程就會將參數層層傳遞到最裏面的classyGreeting開始執行,從而完成函數的依次執行。ramda.js本身實現了reduce,不只支持數組的reduce,還支持多種數據結構的reduce,(兼容性也更好?),下一步來分析是如何本身實現數組的reduce的,可與看出,本身分離出來邏輯以後,函數的執行過程和組合的規則部分將分離的更完全。

3.本身寫一個reduce

reduce接受三個參數,執行函數,初始值,執行隊列(能夠不止爲一個數組),返回一個針對這些參數的reduce處理,這裏只寫數組部分(_arrayReduce),源碼中還包含了關於迭代器的_iterableReduce 等等,並且ramda.js對執行函數也有一層對象封裝,擴展了函數的功能

var reduce = (fn, acc, list) => (fn = _xwrap(fn), _arrayReduce(fn, acc, list))

在寫_arrayReduce以前,先來看一下函數的對象封裝_xwrap

var _xwrap = (function(){
    function XWrap(fn) {
        this.f = fn;
    }
    XWrap.prototype['@@transducer/init'] = function() {
        throw new Error('init not implemented on XWrap');
    };
    XWrap.prototype['@@transducer/result'] = function(acc) {
        return acc;
    };
    XWrap.prototype['@@transducer/step'] = function(acc, x) {
        return this.f(acc, x);
    };
    return function _xwrap(fn) { return new XWrap(fn); };
})()

其實就是對函數執行狀態作了一個分類管理
@@transducer/step 這種狀態認爲是一種過程狀態
@@transducer/result 這種狀態被認爲是一種結果狀態
這種狀態管理經過對象也是合情合理的
最後再來完成_arrayReduce,就很簡單了,這個函數只是專心一件事情,就是寫reduce的過程規則。

var _arrayReduce = (xf, acc, list) => {
    var idx = 0
    var len = list.length
    while (idx < len) {
        acc = xf['@@transducer/step'](acc, list[idx]);
        idx += 1;
    }
    return xf['@@transducer/result'](acc);
}

至此,ramda.js簡化版的reduce就完成了。

4.其餘一些功能

tail用來分離初始值和執行隊列的,由於初始函數是多元的(接收多個參數),執行隊列都是一元(接收一個參數)的,分離仍是有必要的

var tail = arr => arr.slice(1)

reverse改變執行順序

var reverse = arr => arr.reverse()

_arity我把源代碼貼出來,我也不知道爲何這樣作,多是明確指定參數吧,由於reduce生成的函數是能夠接受多個參數的,_arity就是處理這個函數的

var _arity = (n, fn) => {
    switch (n) {
    case 0: return function() { return fn.apply(this, arguments); };
    case 1: return function(a0) { return fn.apply(this, arguments); };
    case 2: return function(a0, a1) { return fn.apply(this, arguments); };
    case 3: return function(a0, a1, a2) { return fn.apply(this, arguments); };
    case 4: return function(a0, a1, a2, a3) { return fn.apply(this, arguments); };
    case 5: return function(a0, a1, a2, a3, a4) { return fn.apply(this, arguments); };
    case 6: return function(a0, a1, a2, a3, a4, a5) { return fn.apply(this, arguments); };
    case 7: return function(a0, a1, a2, a3, a4, a5, a6) { return fn.apply(this, arguments); };
    case 8: return function(a0, a1, a2, a3, a4, a5, a6, a7) { return fn.apply(this, arguments); };
    case 9: return function(a0, a1, a2, a3, a4, a5, a6, a7, a8) { return fn.apply(this, arguments); };
    case 10: return function(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) { return fn.apply(this, arguments); };
    default: throw new Error('First argument to _arity must be a non-negative integer no greater than ten');
  }
}

5.整合

最後整合出來兩個最終的函數pipecompose

var pipe = (...args) => _arity(args[0].length, reduce(_pipe, args[0], tail(args)))
var remdaCompose = (...args) => pipe.apply(this, reverse(args))

再把上面的demo試一下

console.log(remdaCompose(repeat, toUpper, classyGreeting)('dong', 'zhe'))
// THE NAME'S ZHE, DONG ZHETHE NAME'S ZHE, DONG ZHE

整合的徹底版我放到了github

總結

這篇文章主要分析了ramda.js實現compose的過程,其中分析瞭如何把函數當作一等公民,如何實現一個reduce等等。能夠看出,compose的實現從頭至尾都是函數式編程的思想,下一篇文章打算結合社區的一道問答題來介紹一下如何用函數式思想來解決問題。我也是初學函數式,有什麼說的不許確的地方但願多多指正。

相關文章
相關標籤/搜索