美文网首页
Ramda.js 入门

Ramda.js 入门

作者: 风之化身呀 | 来源:发表于2020-02-26 21:54 被阅读0次

    1、前言

    Ramda.js 是函数式编程领域中类似 lodash、underscore 的存在。在函数式编程流行的现在,Ramda.js 是一个非常给力的工具库。本文尝试分析它的源码(v0.27.0)以加深对函数式编程的理解。

    • 目录结构



      dist,es,src 目录都是通过脚本生成的,真正的源文件是 source 目录,其他的文件夹分为测试和辅助脚本两类。本文关心点就是 source 目录:



      其中 internal 下有 74 个辅助函数,功能函数共有 332 个
    • 设计哲学

    为了严格遵守单一职责,Ramda 设计时采用 “function first,data last” 的方式来设计功能函数,这样做是为了方便函数组合。

    // lodash 版
    var sum = function(list) {
        return _.reduce(list, add, 0);
    };
    var total = function(basket) {
        return sum(_.pluck(basket, 'cost'));
    };
    
    // Ramda 版
    var sum = R.reduce(add, 0);
    var total = R.compose(sum, R.pluck('cost'));
    

    2、内部典型函数

    • _arity
      由于JS中函数参数数量无限制,因此一个函数是几元的不好定义,所以提供这个函数,用于将一个函数改成几元函数的形式。只是形式上的约束,参数个数还是任意的
    function _arity(n, fn) {
      /* eslint-disable no-unused-vars */
      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');
      }
    }
    
    • _curry1
    function _curry1(fn) {
      return function f1(a) {
        if (arguments.length === 0 || _isPlaceholder(a)) {
          return f1;
        } else {
          return fn.apply(this, arguments);
        }
      };
    }
    
    • _curry2
    function _curry2(fn) {
      return function f2(a, b) {
        switch (arguments.length) {
          case 0:
            return f2;
    
          case 1:
            return _isPlaceholder(a) ? f2 : _curry1(function (_b) {
              return fn(a, _b);
            });
    
          default:
            return _isPlaceholder(a) && _isPlaceholder(b) ? f2 : _isPlaceholder(a) ? _curry1(function (_a) {
              return fn(_a, b);
            }) : _isPlaceholder(b) ? _curry1(function (_b) {
              return fn(a, _b);
            }) : fn(a, b);
        }
      };
    }
    
    • _curryN
    function _curryN(length, received, fn) {
      return function () {
        var combined = [];
        var argsIdx = 0;
        var left = length;
        var combinedIdx = 0;
    
        while (combinedIdx < received.length || argsIdx < arguments.length) {
          var result;
    
          if (combinedIdx < received.length && (!_isPlaceholder(received[combinedIdx]) || argsIdx >= arguments.length)) {
            result = received[combinedIdx];
          } else {
            result = arguments[argsIdx];
            argsIdx += 1;
          }
    
          combined[combinedIdx] = result;
    
          if (!_isPlaceholder(result)) {
            left -= 1;
          }
    
          combinedIdx += 1;
        }
    
        return left <= 0 ? fn.apply(this, combined) : _arity(left, _curryN(length, combined, fn));
      };
    }
    
    • _clone
    function _clone(value, refFrom, refTo, deep) {
      var copy = function copy(copiedValue) {
        var len = refFrom.length;
        var idx = 0;
    
        while (idx < len) {
          if (value === refFrom[idx]) {
            return refTo[idx];
          }
    
          idx += 1;
        }
    
        refFrom[idx + 1] = value;
        refTo[idx + 1] = copiedValue;
    
        for (var key in value) {
          copiedValue[key] = deep ? _clone(value[key], refFrom, refTo, true) : value[key];
        }
    
        return copiedValue;
      };
    
      switch (type(value)) {
        case 'Object':
          return copy({});
    
        case 'Array':
          return copy([]);
    
        case 'Date':
          return new Date(value.valueOf());
    
        case 'RegExp':
          return _cloneRegExp(value);
    
        default:
          return value;
      }
    }
    
    • _pipe
    function _pipe(f, g) {
      return function () {
        return g.call(this, f.apply(this, arguments));
      };
    }
    
    // pipe 基于此实现
    function pipe() {
      if (arguments.length === 0) {
        throw new Error('pipe requires at least one argument');
      }
    
      return _arity(arguments[0].length, reduce(_pipe, arguments[0], tail(arguments)));
    }
    

    3、常用函数

    • pluck
      从列表内的每个对象元素中取出特定名称的属性,组成一个新的列表。
    // 方式1
    var getAges = R.pluck('age');
    getAges([{name: 'fred', age: 29}, {name: 'wilma', age: 27}]); //=> [29, 27]
    // 方式2
    R.pluck('age',[{name: 'fred', age: 29}, {name: 'wilma', age: 27}]); // =>[29,27]
    
    • pick
      返回对象的部分拷贝,其中仅包含指定键对应的属性。如果某个键不存在,则忽略该属性。
    R.pick(['a', 'd'], {a: 1, b: 2, c: 3, d: 4}); //=> {a: 1, d: 4}
    R.pick(['a', 'e', 'f'], {a: 1, b: 2, c: 3, d: 4}); //=> {a: 1}
    
    • scan
      reduce 类似,但会将每次迭代计算的累积值记录下来,组成一个列表返回
    const numbers = [1, 2, 3, 4];
    const factorials = R.scan(R.multiply, 1, numbers); //=> [1, 1, 2, 6, 24]
    
    • prop
      取出对象中指定属性的值。如果不存在,则返回 undefined
    R.prop('x', {x: 100}); //=> 100
    R.prop('x')({x:100}); //=> 100
    
    • propEq
      如果指定对象属性与给定的值相等,则返回 true ;否则返回 false 。
    const abby = {name: 'Abby', age: 7, hair: 'blond'};
    const fred = {name: 'Fred', age: 12, hair: 'brown'};
    const rusty = {name: 'Rusty', age: 10, hair: 'brown'};
    const alois = {name: 'Alois', age: 15, disposition: 'surly'};
    const kids = [abby, fred, rusty, alois];
    const hasBrownHair = R.propEq('hair', 'brown');
    R.filter(hasBrownHair, kids); //=> [fred, rusty]
    
    • compose
      从右往左执行函数组合(右侧函数的输出作为左侧函数的输入)。最后一个函数可以是任意元函数(参数个数不限),其余函数必须是一元函数
    R.compose(Math.abs, R.add(1), R.multiply(2))(-4) //=> 7
    
    • pipe
      从左往右执行函数组合。第一个函数可以是任意元函数(参数个数不限),其余函数必须是一元函数。
    R.pipe(R.multiply(2)(-4),Math.abs, R.add(1)) //=> 7
    
    • curry
      对函数进行柯里化
    // 如果 f 是三元函数,g 是 R.curry(f),则如下写法都是等价的
    g(1)(2)(3)
    g(1)(2, 3)
    g(1, 2)(3)
    g(1, 2, 3)
    g(_, 2, 3)(1)
    g(_, _, 3)(1)(2)
    g(_, _, 3)(1, 2)
    g(_, 2)(1)(3)
    g(_, 2)(1, 3)
    g(_, 2)(_, 3)(1)
    
    • clone
      深度克隆
    const objects = [{}, {}, {}];
    const objectsClone = R.clone(objects);
    objects === objectsClone; //=> false
    objects[0] === objectsClone[0]; //=> false
    
    // 实现
    _clone(value, [], [], true)
    

    参考

    相关文章

      网友评论

          本文标题:Ramda.js 入门

          本文链接:https://www.haomeiwen.com/subject/frvjactx.html