资讯专栏INFORMATION COLUMN

ramda.js的compose源码解析

ZweiZhao / 756人阅读

摘要:至此,简化版的就完成了。可以看出,的实现从头到尾都是函数式编程的思想,下一篇文章打算结合社区的一道问答题来介绍一下如何用函数式思想来解决问题。我也是初学函数式,有什么说的不准确的地方希望多多指正。

前言

上一篇文章介绍了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的实现从头到尾都是函数式编程的思想,下一篇文章打算结合社区的一道问答题来介绍一下如何用函数式思想来解决问题。我也是初学函数式,有什么说的不准确的地方希望多多指正。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81755.html

相关文章

  • JavaScript专题之函数组合

    摘要:专题系列第十六篇,讲解函数组合,并且使用柯里化和函数组合实现模式需求我们需要写一个函数,输入,返回。这便是函数组合。 JavaScript 专题系列第十六篇,讲解函数组合,并且使用柯里化和函数组合实现 pointfree 模式 需求 我们需要写一个函数,输入 kevin,返回 HELLO, KEVIN。 尝试 var toUpperCase = function(x) { return...

    周国辉 评论0 收藏0
  • 关于javascript函数式编程中compose实现

    摘要:结论这次主要介绍了函数式编程中的函数的原理和实现方法,由于篇幅原因,我把打算分析的源码实现放到下一篇来介绍,可以说实现的更加函数式,需要单独好好分析。 上一篇文章介绍了javascript函数式编程中curry(柯里化)的实现,当然那个柯里化是有限参数的柯里化,等有机会在补上无限参数的那一种柯里化,这次主要说的是javascript函数式编程中另外一个很重要的函数compose,com...

    jonh_felix 评论0 收藏0
  • Lodash 源码分析(三)Array

    摘要:前言这是源码分析系列文章的第三篇,前面两篇文章源码分析一源码分析二分别分析了中的一些重要函数,也给出了简化的实现,为理解其内部机理和执行方式提供了便利。官方也对其进行了说明。 前言 这是Lodash源码分析系列文章的第三篇,前面两篇文章(Lodash 源码分析(一)Function Methods、Lodash 源码分析(二)Function Methods)分别分析了Lodash F...

    ZoomQuiet 评论0 收藏0
  • redux源码解读--compose源码解析

    摘要:源码解析模块的代码十分简练,但是实现的作用却是十分强大。只传递一个参数的时候,就直接把这个函数返回返回组合函数这就是对源码的一个整体解读,水平有限,欢迎拍砖。后续的源码解读和测试例子可以关注源码解读仓库 compose源码解析 compose模块的代码十分简练,但是实现的作用却是十分强大。redux为何称为redux?有人说就是reduce和flux的结合体,而reduce正是comp...

    lk20150415 评论0 收藏0
  • koa源码解析

    摘要:用法回顾执行顺序每当执行时,执行下一个中间件,执行到最后一个中间件后开始往回执行源码解析源码执行步骤使用方法即将进中的数组中方法调用的和方法来创建服务,的回掉执行下面的操作回掉首先执行方法将组合成一个对象来执行,这个对象即可完成中 用法回顾 const Koa = require(koa); const app = new Koa(); app.use(async (ctx, nex...

    hsluoyz 评论0 收藏0

发表评论

0条评论

阅读需要支付1元查看
<