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)
网友评论