上一篇文章介绍了javascript
中的compose
函数的实现,我是用了递归的思想去让函数依次执行,lodash
中是用了迭代的思想依次执行函数,但实现了之后我仍是以为有些别扭,仔细想一想,咱们实现的是一个函数式编程用到的函数,可是实现的方法仍是太命令式了,函数仍是命令式的执行,通俗点说,仍是太把函数当成函数了,在个人理解中,函数和普通变量没什么区别,只是执行的方法不同,一旦赋予了函数这个执行的属性,咱们就能够彻底将函数当成普通变量去对待。javascript
函数和普通变量没什么区别,只是须要偶尔执行一下java
举个例子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
在这里提到了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
app
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
组合classyGreeting
,toUpper
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
的,可与看出,本身分离出来逻辑以后,函数的执行过程和组合的规则部分将分离的更完全。
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
就完成了。
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'); } }
最后整合出来两个最终的函数pipe
和compose
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
的实现从头至尾都是函数式编程的思想,下一篇文章打算结合社区的一道问答题来介绍一下如何用函数式思想来解决问题。我也是初学函数式,有什么说的不许确的地方但愿多多指正。