ramda.js的compose源码剖析

媒介

上一篇文章引见了javascript中的compose函数的完成,我是用了递归的头脑去让函数顺次实行,lodash中是用了迭代的头脑顺次实行函数,但完成了今后我照样以为有些别扭,细致想一想,我们完成的是一个函数式编程用到的函数,然则完成的要领照样太敕令式了,函数照样敕令式的实行,浅显点说,照样太把函数当做函数了,在我的明白中,函数和一般变量没什么区分,只是实行的要领不一样,一旦给予了函数这个实行的属性,我们就能够完整将函数当做一般变量去看待。

函数和一般变量没什么区分,只是须要偶然实行一下

完成

1.函数天下的加号

举个例子

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

前两个例子就是一般变量的操纵,末了一个例子是函数的操纵,本质上看来,没有任何区分,两个函数作用的结果就是天生一个函数,只不过在函数的天下里,这个加号的意义就是怎样变更天生一个新的函数,回到compose来,在compose中,加号的意义就是把一个函数的实行结果当做下一个函数的输入,末了在天生一个函数,就像下面如许

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中所谓加号的意义了。

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,再repeat

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的完成从头至尾都是函数式编程的头脑,下一篇文章盘算连系社区的一道问答题来引见一下怎样用函数式头脑来解决问题。我也是初学函数式,有什么说的不正确的处所愿望多多斧正。

    原文作者:dongzhe3917875
    原文地址: https://segmentfault.com/a/1190000008479809
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞