美文网首页
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