ramda整理
    ramda柯里化实现(源码)
    柯里化简单实现
    一个参数柯里化实现
    function _curry1(fn) {
    return function f1(a) {
    if (arguments.length === 0) {
    return f1;
    } else {
    return fn.apply(this, arguments);
    }
    };
    }
    <br />
    const curryMathAbs = _curry1(Math.abs);
    curryMathAbs(-3) // 3
    curryMathAbs()(-3) // 3

    二个参数柯里化实现
    function _curry2(fn) {
    return function f2(a, b) {
    switch (arguments.length) {
    case 0:
    return f2;
    case 1:
    return _curry1(function(_b) { return fn(a, _b); });
    default:
    return fn(a, b);
    }
    };
    }

    三个参数柯里化实现
    function _curry3(fn) {
    return function f3(a, b, c) {
    switch (arguments.length) {
    case 0:
    return f3;
    <br />
    case 1:
    return _curry2(function (_b, _c) {
    return fn(a, _b, _c);
    });
    <br />
    case 2:
    return _curry1(function (_c) {
    return fn(a, b, _c);
    });
    <br />
    default:
    return fn(a, b, c);
    }
    };
    }

    传入占位符的柯里化实现
    想要的效果
    g = curry(f)
    g(1)(2)(3)
    <br />
    也可以
    g2 = g(_)(2)(3)
    g2(1)
    方案
    // 示例
    R.add(R.__, 2)(3)
    <br />
    // R.__表示占位符,源码__.js
    module.exports = {
    '@@functional/placeholder': true
    };
    <br />
    // 判断是否是占位符,源码_isPlaceholder.js
    function _isPlaceholder(a) {
    return a != null && typeof a === 'object' && a['@@functional/placeholder'] === true;
    }

    一个参数柯里化实现
    function _curry1(fn) {
    return function f1(a) {
    // 若执行函数,无参数或传入占位符,则返回原函数
    // curryMathAbs = _curry1(Math.abs);
    // curryMathAbs(-3) // 3
    // curryMathAbs()(-3) // 3
    if (arguments.length === 0 || _isPlaceholder(a)) {
    return f1;
    } else {
    return fn.apply(this, arguments);
    }
    };
    }

    二个参数柯里化实现
    function _curry2(fn) {
    return function f2(a, b) {
    switch (arguments.length) {
    case 0:
    return f2;
    <br />
    case 1:
    // 若传入1个参数,且为占位符,则返回原函数,参照【一个参数柯里化实现】示例
    // 否则调用_curry1,且fn函数的参数为_b,为后续参数
    // R.add(7)(10);
    return _isPlaceholder(a) ? f2 : _curry1(function (_b) {
    return fn(a, _b);
    });
    <br />
    default:
    // 若全是占位符则返回原函数,参照【一个参数柯里化实现】示例
    // 若a传入占位符,调用_curry1,且fn函数的参数为_a,将后续参数当做a再次传入;同理b为占位符
    // R.add(7)(10);
    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);
    }
    };
    }

    三个参数柯里化实现
    function _curry3(fn) {
    return function f3(a, b, c) {
    switch (arguments.length) {
    case 0:
    return f3;
    <br />
    case 1:
    // 若传入1个参数,且为占位符,则返回原函数,参照【一个参数柯里化实现】示例
    // 否则调用_curry2,且fn函数的参数为_b, _c,为后续参数
    return _isPlaceholder(a) ? f3 : _curry2(function (_b, _c) {
    return fn(a, _b, _c);
    });
    <br />
    case 2:
    // 若全是占位符则返回原函数
    // 若a传入占位符,调用_curry2,且fn函数的参数为_a, _c,将后续参数作为相应位置数据传入;同理b传入占位符,调用_curry2,且fn函数的参数为_b, _c,将后续参数作为相应位置数据传入;否则调用_curry1,且fn函数的参数为_c, 为后续参数
    // R.add(7)(10);
    return _isPlaceholder(a) && _isPlaceholder(b) ? f3 : _isPlaceholder(a) ? _curry2(function (_a, _c) {
    return fn(_a, b, _c);
    }) : _isPlaceholder(b) ? _curry2(function (_b, _c) {
    return fn(a, _b, _c);
    }) : _curry1(function (_c) {
    return fn(a, b, _c);
    });
    <br />
    default:
    // 同理上述,排列组合
    return _isPlaceholder(a) && _isPlaceholder(b) && _isPlaceholder(c) ? f3 : _isPlaceholder(a) && _isPlaceholder(b) ? _curry2(function (_a, _b) {
    return fn(_a, _b, c);
    }) : _isPlaceholder(a) && _isPlaceholder(c) ? _curry2(function (_a, _c) {
    return fn(_a, b, _c);
    }) : _isPlaceholder(b) && _isPlaceholder(c) ? _curry2(function (_b, _c) {
    return fn(a, _b, _c);
    }) : _isPlaceholder(a) ? _curry1(function (_a) {
    return fn(_a, b, c);
    }) : _isPlaceholder(b) ? _curry1(function (_b) {
    return fn(a, _b, c);
    }) : _isPlaceholder(c) ? _curry1(function (_c) {
    return fn(a, b, _c);
    }) : fn(a, b, c);
    }
    };
    }

    多个参数演进及实现
    通常的柯里化的实现:
    function curry (fn) {
    return function f() {
    const args = [].slice.call(arguments);
    if(args.length < fn.length) {
    return f.apply(this, args.concat([].slice.call(arguments)))
    } else {
    return fn.apply(this, args);
    }
    }
    }
    <br />

    缺点:

    • 调用柯里化的函数后无法知道执行了部分参数的函数,还需要几个参数。
    • 传入参数位置必须和函数接受的参数位置保持一致

    优化多个参数过程:
    最终结果(可以放在控制台自行运行)
    function _arity(n, fn) {
    /* eslint-disable no-unused-vars */
    switch (n) {
    case 0:
    return function () {
    return fn.apply(this, arguments);
    };
    <br />
    case 1:
    return function (a0) {
    return fn.apply(this, arguments);
    };
    <br />
    case 2:
    return function (a0, a1) {
    return fn.apply(this, arguments);
    };
    <br />
    case 3:
    return function (a0, a1, a2) {
    return fn.apply(this, arguments);
    };
    <br />
    case 4:
    return function (a0, a1, a2, a3) {
    return fn.apply(this, arguments);
    };
    <br />
    case 5:
    return function (a0, a1, a2, a3, a4) {
    return fn.apply(this, arguments);
    };
    <br />
    case 6:
    return function (a0, a1, a2, a3, a4, a5) {
    return fn.apply(this, arguments);
    };
    <br />
    case 7:
    return function (a0, a1, a2, a3, a4, a5, a6) {
    return fn.apply(this, arguments);
    };
    <br />
    case 8:
    return function (a0, a1, a2, a3, a4, a5, a6, a7) {
    return fn.apply(this, arguments);
    };
    <br />
    case 9:
    return function (a0, a1, a2, a3, a4, a5, a6, a7, a8) {
    return fn.apply(this, arguments);
    };
    <br />
    case 10:
    return function (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {
    return fn.apply(this, arguments);
    };
    <br />
    default:
    throw new Error('First argument to _arity must be a non-negative integer no greater than ten');
    }
    }
    var _ = { '@@function/placeholder' : true};
    function _isPlaceholder(a) {
    return a != null && typeof a === 'object' && a['@@function/placeholder'] === true;
    }
    <br />
    function _curry1(fn) {
    return function f1(a) {
    if (arguments.length === 0 || _isPlaceholder(a)) {
    return f1;
    } else {
    return fn.apply(this, arguments);
    }
    };
    }
    <br />
    function _curry2(fn) {
    return function f2(a, b) {
    switch (arguments.length) {
    case 0:
    return f2;
    <br />
    case 1:
    return _isPlaceholder(a) ? f2 : _curry1(function (_b) {
    return fn(a, _b);
    });
    <br />
    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);
    }
    };
    }
    <br />
    function _curryN(length, received, fn) {
    return function () {
    var combined = [];
    var argsIdx = 0;
    var left = length;
    var combinedIdx = 0;
    console.log('jinlai', combinedIdx, received, argsIdx, arguments)
    while (combinedIdx < received.length || argsIdx < arguments.length) {
    var result;
    console.log('循环', combinedIdx, received, !_isPlaceholder(received[combinedIdx]), argsIdx, arguments, combinedIdx < received.length && (!_isPlaceholder(received
    <br />
    [combinedIdx]) || argsIdx >= arguments.length))
    <br />
    if (combinedIdx < received.length && (!_isPlaceholder(received[combinedIdx]) || argsIdx >= arguments.length)) {
    result = received[combinedIdx];
    } else {
    result = arguments[argsIdx];
    argsIdx += 1;
    }
    <br />
    combined[combinedIdx] = result;
    <br />
    if (!_isPlaceholder(result)) {
    left -= 1;
    }
    <br />
    console.log('jiesu', left, combined)
    <br />
    combinedIdx += 1;
    }
    <br />
    return left <= 0 ? fn.apply(this, combined) : _arity(left, _curryN(length, combined, fn));
    };
    }
    <br />
    var curryN =
    _curry2(function curryN(length, fn) {
    if (length === 1) {
    return _curry1(fn);
    }
    <br />
    return _arity(length, _curryN(length, [], fn));
    });
    <br />
    function say(name, age, like) { console.log(我叫${name},我${age}岁了, 我喜欢${like}) };
    var msg = curryN(3, say)
    msg(_, 20)('大西瓜', _,) ('妹子') // 我叫大西瓜,我20岁了, 我喜欢妹子
    msg(_, _, '瞎bb')(_, '25')('小hb') // 我叫小hb,我25岁了, 我喜欢瞎bb
    msg('小明')(_, _)(22, '小红') // 我叫小明,我22岁了, 我喜欢小红

    step1: 解决【调用柯里化的函数后无法知道执行了部分参数的函数,还需要几个参数】
    // 源码_arity.js
    // 包裹一个函数,返回一个确定参数的函数。一般来说,函数的复杂度是和他自身的参数成正比的,函数接收的函数越多,那么函数的复杂度就越高,虽然JavaScript中没有明确规定的传入参数的个数(好像是225个?),但是这里限制如果一个函数的参数超过10个那么就抛出错误
    function _arity(n, fn) {
    /* eslint-disable no-unused-vars */
    switch (n) {
    case 0:
    return function () {
    return fn.apply(this, arguments);
    };
    <br />
    case 1:
    return function (a0) {
    return fn.apply(this, arguments);
    };
    <br />
    case 2:
    return function (a0, a1) {
    return fn.apply(this, arguments);
    };
    <br />
    case 3:
    return function (a0, a1, a2) {
    return fn.apply(this, arguments);
    };
    <br />
    case 4:
    return function (a0, a1, a2, a3) {
    return fn.apply(this, arguments);
    };
    <br />
    case 5:
    return function (a0, a1, a2, a3, a4) {
    return fn.apply(this, arguments);
    };
    <br />
    case 6:
    return function (a0, a1, a2, a3, a4, a5) {
    return fn.apply(this, arguments);
    };
    <br />
    case 7:
    return function (a0, a1, a2, a3, a4, a5, a6) {
    return fn.apply(this, arguments);
    };
    <br />
    case 8:
    return function (a0, a1, a2, a3, a4, a5, a6, a7) {
    return fn.apply(this, arguments);
    };
    <br />
    case 9:
    return function (a0, a1, a2, a3, a4, a5, a6, a7, a8) {
    return fn.apply(this, arguments);
    };
    <br />
    case 10:
    return function (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {
    return fn.apply(this, arguments);
    };
    <br />
    default:
    throw new Error('First argument to _arity must be a non-negative integer no greater than ten');
    }
    }

    step2: 多函数柯里化版本1(进阶过程的演变,源码与此不同)
    function curry (length, recived, fn) {
    return function() {
    var args = [].slice.call(arguments);
    var combined = recived.concat(args);
    `<br /> if(combined.length < length ) {<br /> return arity(length - combined.length, curry(length, combined, fn)); <br /> } else {<br /> return fn.apply(this, combined);<br /> }<br /> }<br /> }<br />
    <br /> const a = (x, y, z) => x+y+z;<br /> const b = curry(3, [], a)(1);<br /> console.log(b.length) //=> 2<br /> <br /> const c = curry(3, [], a)(1, 2);<br /> console.log(c.length) //=> 1<br /> <br /> const d = curry(3, [], a)(1)(2);<br /> console.log(d) //=> 1`
    结论:以上可以获取剩余参数个数,解决问题一

    step3: 解决【传入参数位置必须和函数接受的参数位置保持一致】
    function _curryN(length, received, fn) {
    return function () {
    // 存放每次调用函数参数的数组
    var combined = [];
    // 入参的循环索引
    var argsIdx = 0;
    // 入参个数
    var left = length;
    // 每次调用函数参数的数组的索引
    var combinedIdx = 0;
    `<br /> //这里同时迭代recived和arguments。 <br /> //我们要循环取出每一次curryN初始化接收到的参数和调用函数时传入的参数保存在combined中 <br /> // 重点难点:将参数进行过滤,保证入参位置统一<br /> while (combinedIdx < received.length || argsIdx < arguments.length) {<br /> var result;<br /> //首先迭代recived,取出不是占位符的参数放入combined中<br /> if (combinedIdx < received.length && (!_isPlaceholder(received[combinedIdx]) || argsIdx >= arguments.length)) {<br /> result = received[combinedIdx];<br /> } else {<br /> //如果recived中不是占位符的参数已经迭代完了,那么将arguments放入combined中<br /> result = arguments[argsIdx];<br /> argsIdx += 1;<br /> }<br />
    <br /> combined[combinedIdx] = result;<br /> //如果当前参数不是占位符,则长度减1<br /> if (!_isPlaceholder(result)) {<br /> left -= 1;<br /> }<br />
    <br /> combinedIdx += 1;<br /> }<br /> //如果传入参数满足fn参数个数,则直接调用fn,否则递归调用curry函数,反复过滤掉recived的占位符<br /> return left <= 0 ? fn.apply(this, combined) : _arity(left, _curryN(length, combined, fn));<br /> };<br />}`

    柯里化使用场景
    参数复用

    • 场景1

    //柯里化实际是把简答的问题复杂化了,但是复杂化的同时,我们在使用函数时拥有了更加多的自由度
    // 示例缺点:我们每次进行校验的时候都需要输入一串正则,再校验同一类型的数据时,相同的正则我们需要写多次, 这就导致我们在使用的时候效率低下,并且由于 checkByRegExp 函数本身是一个工具函数并没有任何意义, 一段时间后我们重新来看这些代码时,如果没有注释,我们必须通过检查正则的内容, 我们才能知道我们校验的是电话号码还是邮箱,还是别的什么
    function checkByRegExp(regExp,string) { return regExp.test(string); }
    // 校验电话号码
    checkByRegExp(/^1\d{10}$/, '18642838455');
    // 校验电话号码
    checkByRegExp(/^1\d{10}$/, '13109840560'); // 校验电话号码
    // 校验邮箱
    checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com');
    // 校验邮箱
    checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@qq.com');

    优化:
    //进行柯里化,实现参数**复用**
    let _check = curry(checkByRegExp);
    //生成工具函数,验证电话号码
    let checkCellPhone = _check(/^1\d{10}$/);
    //生成工具函数,验证邮箱
    let checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
    <br />
    checkCellPhone('18642838455'); // 校验电话号码
    checkCellPhone('13109840560'); // 校验电话号码
    <br />
    checkEmail('test@163.com'); // 校验邮箱
    checkEmail('test@qq.com'); // 校验邮箱

    • 场景2

    //需要获取数据中的所有 name 属性的值,常规思路下,我们会这样实现
    let names = list.map(function(item) {
    return item.name;
    })

    优化:
    let prop = curry(function(key,obj) {
    return obj[key];
    })
    let names = list.map(prop('name'))

    延迟运行
    ramda因为不是返回运算结果,而是返回新函数,所以是延迟运行。例如bind就是延迟执行的代表,不赘述

    扁平化
    // 函数更加易读
    var curry = require('curry');
    var get = curry(function(property, object){ return object[property] });
    <br />
    fetchFromServer()
    .then(JSON.parse)
    .then(get('posts'))
    .then(map(get('title')))

    ramda文档归类

    • 算术

    // 加
    R.add(2, 3); //=> 5
    R.add(7)(10); //=> 17
    <br />
    // 自动加1
    R.inc(42); //=> 43
    <br />
    // 减
    R.subtract(10, 8); //=> 2
    const minus5 = R.subtract(R.__, 5);
    minus5(17); //=> 12
    <br />
    // 自动减1
    R.dec(42); //=> 41
    <br />
    // 乘
    const double = R.multiply(2);
    double(3); //=> 6
    <br />
    // 除
    R.divide(71, 100); //=> 0.71
    <br />
    // 累加
    R.sum([2,4,6,8,100,1]); //=> 121
    <br />
    //累乘
    R.product([2,4,6,8,100,1]); //=> 38400
    <br />
    // 算最大值
    R.max(789, 123); //=> 789
    R.max('a', 'b'); //=> 'b'
    <br />
    //包裹函数判断最大值
    const square = n => n * n;
    R.maxBy(square, -3, 2); //=> -3
    R.reduce(R.maxBy(square), 0, [3, -5, 4, 1, -2]); //=> -5
    <br />
    // 算最小值
    R.min(789, 123); //=> 123
    R.min('a', 'b'); //=> 'a'
    <br />
    //包裹函数判断最小值
    const square = n => n * n;
    R.minBy(square, -3, 2); //=> 2
    R.reduce(R.minBy(square), Infinity, [3, -5, 4, 1, -2]); //=> 1
    <br />
    // 返回给定数字列表的平均值。
    R.mean([2, 7, 9]); //=> 6
    R.mean([]); //=> NaN
    <br />
    // 返回给定数字列表的中位数。
    R.median([2, 9, 7]); //=> 7
    R.median([7, 2, 10, 9]); //=> 8
    R.median([]); //=> NaN
    <br />
    // mathMod 和算术取模操作类似,而不像 % 操作符 (或 [R.modulo](https://ramda.cn/docs/#modulo))。所以 -17 % 5 等于 -2,而 mathMod(-17, 5) 等于 3 。mathMod 要求参数为整型,并且当模数等于 0 或者负数时返回 NaN 。
    R.mathMod(-17, 5); //=> 3
    R.mathMod(17, 5); //=> 2
    R.mathMod(17, -5); //=> NaN
    R.mathMod(17, 0); //=> NaN
    R.mathMod(17.2, 5); //=> NaN
    R.mathMod(17, 5.3); //=> NaN
    <br />
    //用第一个参数除以第二个参数,并返回余数。注意,该函数是 JavaScript-style 的求模操作。数学求模另见 mathMod。
    R.modulo(17, 3); //=> 2
    // JS behavior:
    R.modulo(-17, 3); //=> -2
    R.modulo(17, -3); //=> 2
    const isOdd = R.modulo(R.__, 2);
    isOdd(42); //=> 0
    isOdd(21); //=> 1

    • 判断

    all, any,none,contains,includes,has,hasIn,hasPath,identical,is,isEmpty,isNil,where, whereEq
    // 如果列表中的所有元素都满足 predicate,则返回 true;否则,返回 false
    const equals3 = R.equals(3);
    R.all(equals3)([3, 3, 3, 3]); //=> true
    <br />
    const lessThan0 = R.flip(R.lt)(0);
    R.any(lessThan0)([1, 2]); //=> false
    <br />
    const isEven = n => n % 2 === 0;
    R.none(isEven, [1, 3, 5, 7, 9, 11]); //=> true
    <br />
    // 只要列表中有一个元素等于指定值,则返回 true;否则返回 false。通过 [R.equals](https://ramda.cn/docs/#equals) 函数进行相等性判断
    R.contains(3, [1, 2, 3]); //=> true
    R.contains(4, [1, 2, 3]); //=> false
    R.contains({ name: 'Fred' }, [{ name: 'Fred' }]); //=> true
    R.contains([42], [[42]]); //=> true
    R.contains('ba', 'banana'); //=>true
    <br />
    // 只要列表中有一个元素等于指定值,则返回 true;否则返回 false。通过 R.equals 函数进行相等性判断。
    R.includes(3, [1, 2, 3]); //=> true
    R.includes(4, [1, 2, 3]); //=> false
    R.includes({ name: 'Fred' }, [{ name: 'Fred' }]); //=> true
    R.includes([42], [[42]]); //=> true
    R.includes('ba', 'banana'); //=>true
    <br />
    // 如果对象自身含有指定的属性,则返回 true;否则返回 false。
    const hasName = R.has('name');
    hasName({name: 'alice'}); //=> true
    hasName({name: 'bob'}); //=> true
    hasName({}); //=> false
    <br />
    // 如果对象自身或其原型链上含有指定的属性,则返回 true;否则返回 false。
    function Rectangle(width, height) {
    this.width = width;
    this.height = height;
    }
    Rectangle.prototype.area = function() {
    return this.width * this.height;
    };
    <br />
    const square = new Rectangle(2, 2);
    R.hasIn('width', square); //=> true
    R.hasIn('area', square); //=> true
    <br />
    // 检查对象中是否存在指定的路径。只检查对象自身的属性。
    R.hasPath(['a', 'b'], {a: {b: 2}}); // => true
    R.hasPath(['a', 'b'], {a: {b: undefined}}); // => true
    R.hasPath(['a', 'b'], {a: {c: 2}}); // => false
    R.hasPath(['a', 'b'], {}); // => false
    <br />
    // 如果两个参数是完全相同,则返回 true,否则返回 false。如果它们引用相同的内存,也认为是完全相同的。NaN 和 NaN 是完全相同的;0 和 -0 不是完全相同的。
    const o = {};
    R.identical(o, o); //=> true
    R.identical(1, 1); //=> true
    R.identical(1, '1'); //=> false
    R.identical([], []); //=> false
    R.identical(0, -0); //=> false
    R.identical(NaN, NaN); //=> true
    <br />
    // 检测一个对象(val)是否是给定构造函数的实例。该函数会依次检测其原型链,如果存在的话。
    R.is(Object, {}); //=> true
    R.is(Number, 1); //=> true
    R.is(Object, 1); //=> false
    R.is(String, 's'); //=> true
    R.is(String, new String('')); //=> true
    R.is(Object, new String('')); //=> true
    R.is(Object, 's'); //=> false
    R.is(Number, {}); //=> false
    <br />
    //检测给定值是否为其所属类型的空值,若是则返回 true ;否则返回 false 。
    R.isEmpty([1, 2, 3]); //=> false
    R.isEmpty([]); //=> true
    R.isEmpty(''); //=> true
    R.isEmpty(null); //=> false
    R.isEmpty({}); //=> true
    R.isEmpty({length: 0}); //=> false
    <br />
    // 检测输入值是否为 null 或 undefined 。
    R.isNil(null); //=> true
    R.isNil(undefined); //=> true
    R.isNil(0); //=> false
    R.isNil([]); //=> false
    <br />
    //接受一个测试规范对象和一个待检测对象,如果测试满足规范,则返回 true,否则返回 false。测试规范对象的每个属性值都必须是 predicate 。每个 predicate 作用于待检测对象对应的属性值,如果所有 predicate 都返回 true,则 where 返回 true,否则返回 false 。
    const pred = R.where({
    a: R.equals('foo'),
    b: R.complement(R.equals('bar')),
    x: R.gt(R.__, 10),
    y: R.lt(R.__, 20)
    });
    pred({a: 'foo', b: 'xxx', x: 11, y: 19}); //=> true
    pred({a: 'xxx', b: 'xxx', x: 11, y: 19}); //=> false
    pred({a: 'foo', b: 'bar', x: 11, y: 19}); //=> false
    pred({a: 'foo', b: 'xxx', x: 10, y: 19}); //=> false
    pred({a: 'foo', b: 'xxx', x: 11, y: 20}); //=> false
    <br />
    // 接受一个测试规范对象和一个待检测对象,如果测试满足规范,则返回 true,否则返回 false。如果对于每一个测试规范对象的属性值,待检测对象中都有一个对应的相同属性值,则 where 返回 true,否则返回 false 。
    const pred = R.whereEq({a: 1, b: 2});
    pred({a: 1}); //=> false
    pred({a: 1, b: 2}); //=> true
    pred({a: 1, b: 2, c: 3}); //=> true
    pred({a: 1, b: 1}); //=> false
    allPass, anyPass
    // 传入包含多个 predicate 的列表,返回一个 predicate:如果给定的参数满足列表中的所有 predicate ,则返回 true
    const isQueen = R.propEq('rank', 'Q');
    const isSpade = R.propEq('suit', '♠︎');
    const isQueenOfSpades = R.allPass([isQueen, isSpade]);
    isQueenOfSpades({rank: 'Q', suit: '♣︎'}); //=> false
    isQueenOfSpades({rank: 'Q', suit: '♠︎'}); //=> true
    <br />
    const isClub = R.propEq('suit', '♣');
    const isSpade = R.propEq('suit', '♠');
    const isBlackCard = R.anyPass([isClub, isSpade]);
    isBlackCard({rank: '10', suit: '♣'}); //=> true
    isBlackCard({rank: 'Q', suit: '♦'}); //=> false
    endsWith, starsWith,eqBy, eqProps,equals
    // 检查列表是否以指定的子列表结尾
    R.endsWith('c', 'abc') //=> true
    R.endsWith('b', 'abc') //=> false
    R.endsWith(['c'], ['a', 'b', 'c']) //=> true
    R.endsWith(['b'], ['a', 'b', 'c']) //=> false
    <br />
    // 检查列表是否以给定的值开头。
    R.startsWith('a', 'abc') //=> true
    R.startsWith('b', 'abc') //=> false
    R.startsWith(['a'], ['a', 'b', 'c']) //=> true
    R.startsWith(['b'], ['a', 'b', 'c']) //=> false
    <br />
    // 接受一个函数和两个值,通过传入函数对两个值进行相等性判断。如果两个值的计算结果相等,则返回 true ;否则返回 false 。
    R.eqBy(Math.abs, 5, -5); //=> true
    <br />
    // 判断两个对象指定的属性值是否相等。通过 [R.equals](https://ramda.cn/docs/#equals) 函数进行相等性判断。可用作柯里化的 predicate 。
    const o1 = { a: 1, b: 2, c: 3, d: 4 };
    const o2 = { a: 10, b: 20, c: 3, d: 40 };
    R.eqProps('a', o1, o2); //=> false
    R.eqProps('c', o1, o2); //=> true
    <br />
    // 如果传入的参数相等,返回 true;否则返回 false。可以处理几乎所有 JavaScript 支持的数据结构。
    R.equals(1, 1); //=> true
    R.equals(1, '1'); //=> false
    R.equals([1, 2, 3], [1, 2, 3]); //=> true
    cond,ifElse, unless,when
    // 返回一个封装了 if / else,if / else, ... 逻辑的函数 fn。 R.cond 接受列表元素为 [predicate,transformer] 的列表。 fn 的所有参数顺次作用于每个 predicate,直到有一个返回 "truthy" 值,此时相应 transformer 对参数处理,并作为 fn 的结果返回。 如果没有 predicate 匹配,则 fn 返回 undefined。
    const fn = R.cond([
    [R.equals(0), R.always('water freezes at 0°C')],
    [R.equals(100), R.always('water boils at 100°C')],
    [R.T, temp => 'nothing special happens at ' + temp + '°C']
    ]);
    fn(0); //=> 'water freezes at 0°C'
    fn(50); //=> 'nothing special happens at 50°C'
    fn(100); //=> 'water boils at 100°C'
    <br />
    // 根据 condition predicate 的返回值调用 onTrue 或 onFalse 函数。
    const incCount = R.ifElse(
    R.has('count'),
    R.over(R.lensProp('count'), R.inc),
    R.assoc('count', 1)
    );
    incCount({}); //=> { count: 1 }
    incCount({ count: 1 }); //=> { count: 2 }
    <br />
    // 判断输入值是否满足 predicate,若不符合,则将输入值传给 whenFalseFn 处理,并将处理结果作为返回;若符合,则将输入值原样返回。
    let safeInc = R.unless(R.isNil, R.inc);
    safeInc(null); //=> null
    safeInc(1); //=> 2
    <br />
    // 判断输入值是否满足 predicate,若符合,则将输入值传给 whenTrueFn 处理,并将处理结果作为返回;若不符合,则将输入值原样返回。
    const truncate = R.when(
    R.propSatisfies(R.gt(R.__, 10), 'length'),
    R.pipe(R.take(10), R.append('…'), R.join(''))
    );
    truncate('12345'); //=> '12345'
    truncate('0123456789ABC'); //=> '0123456789…'
    gt,lt,gte,lte
    R.gt(2, 1); //=> true
    R.gt(2, 2); //=> false
    <br />
    R.lt(2, 1); //=> false
    R.lt(2, 2); //=> false
    <br />
    R.gte(2, 1); //=> true
    R.gte(2, 2); //=> true
    <br />
    R.lte(2, 1); //=> false
    R.lte(2, 2); //=> true

    • 逻辑方法

    //如果两个参数都是 true,则返回 true;否则返回 false, 相当于&&
    R.and(true, true); //=> true
    R.and(true, false); //=> false
    R.and(false, true); //=> false
    R.and(false, false); //=> false
    <br />
    //如果其中一个参数为真,另一个参数为假,则返回true ;否则返回false,异或操作
    R.xor(true, true); //=> false
    R.xor(true, false); //=> true
    R.xor(false, true); //=> true
    R.xor(false, false); //=> false
    <br />
    //只要有一个参数为真(truth-y),就返回 true;否则返回 false,相当于||
    R.or(true, true); //=> true
    R.or(true, false); //=> true
    R.or(false, true); //=> true
    R.or(false, false); //=> false
    <br />
    // 该函数调用两个函数,并对两函数返回值进行与操作。若第一个函数结果为 false-y 值 (false, null, 0 等),则返回该结果,否则返回第二个函数的结果。注意,both 为短路操作,即如果第一个函数返回 false-y 值,则不会调用第二个函数
    const gt10 = R.gt(R.__, 10)
    const lt20 = R.lt(R.__, 20)
    const f = R.both(gt10, lt20);
    f(15); //=> true
    f(30); //=> false
    <br />
    // 返回由 || 运算符连接的两个函数的包装函数。如果两个函数中任一函数的执行结果为 truth-y,则返回其执行结果。 注意,这个是短路表达式,意味着如果第一个函数返回 truth-y 值的话,第二个函数将不会执行。
    const gt10 = x => x > 10;
    const even = x => x % 2 === 0;
    const f = R.either(gt10, even);
    f(101); //=> true
    f(8); //=> true
    <br />
    // 对函数的返回值取反。接受一个函数 f,返回一个新函数 g:在输入参数相同的情况下,若 f 返回 'true-y' ,则 g 返回 false-y ,反之亦然
    const isNotNil = R.complement(R.isNil);
    R.isNil(null); //=> true
    isNotNil(null); //=> false
    R.isNil(7); //=> false
    isNotNil(7); //=> true
    <br />
    R.not(true); //=> false
    R.not(false); //=> true
    R.not(0); //=> true
    R.not(1); //=> false
    <br />
    // 反操作
    R.negate(42); //=> -42

    • 循环,过滤等

    addIndex
    // 例如,addIndex 可以将 [R.map](https://ramda.cn/docs/#map) 转换为类似于 Array.prototype.map 的函数。注意,addIndex 只适用于迭代回调函数是首个参数、列表是最后一个参数的函
    const mapIndexed = R.addIndex(R.map);
    mapIndexed((val, idx) => idx + '-' + val, {x: 1, y: 2, z: 3});
    // {x: "0-1", y: "1-2", z: "2-3"}
    <br />
    const mapIndexed = R.addIndex(R.map);
    mapIndexed((val, idx) => idx + '-' + val, ['f', 'o', 'o', 'b', 'a', 'r']);
    //=> ['0-f', '1-o', '2-o', '3-b', '4-a', '5-r']
    forEach, forEachObjIndexed,map, mapObjIndexed,mapAccum, mapAccumRight
    // 遍历 list,对 list 中的每个元素执行方法 fn。
    const printXPlusFive = x => console.log(x + 5);
    R.forEach(printXPlusFive, [1, 2, 3]); //=> [1, 2, 3]
    <br />
    // 遍历 object,对 object 中的每对 key 和 value 执行方法 fn。
    const printKeyConcatValue = (value, key) => console.log(key + ':' + value);
    R.forEachObjIndexed(printKeyConcatValue, {x: 1, y: 2}); //=> {x: 1, y: 2}
    // logs x:1
    // logs y:2
    <br />
    // 接收一个函数和一个 [functor](https://github.com/fantasyland/fantasy-land#functor), 将该函数应用到 functor 的每个值上,返回一个具有相同形态的 functor。
    const double = x => x * 2;
    R.map(double, [1, 2, 3]); //=> [2, 4, 6]
    R.map(double, {x: 1, y: 2, z: 3}); //=> {x: 2, y: 4, z: 6}
    <br />
    // Object 版本的 [map](https://ramda.cn/docs/#map)。mapping function 接受三个参数: _(value, key, obj)_ 。如果仅用到参数 _value_,则用 [map](https://ramda.cn/docs/#map) 即可。
    const xyz = { x: 1, y: 2, z: 3 };
    const prependKeyAndDouble = (num, key, obj) => key + (num * 2);
    R.mapObjIndexed(prependKeyAndDouble, xyz); //=> { x: 'x2', y: 'y4', z: 'z6' }
    <br />
    // mapAccum 的行为类似于 map 和 reduce 的组合;它将迭代函数作用于列表中的每个元素,从左往右传递经迭代函数计算的累积值,并将最后的累积值和由所有中间的累积值组成的列表一起返回。 迭代函数接收两个参数,_acc_ 和 _value_, 返回一个元组 _[acc, value]_。
    const digits = ['1', '2', '3', '4'];
    const appender = (a, b) => [a + b, a + b];
    R.mapAccum(appender, 0, digits); //=> ['01234', ['01', '012', '0123', '01234']]
    <br />
    // mapAccumRight 的行为类似于 map 和 reduce 的组合;它将迭代函数作用于列表中的每个元素,从右往左传递经迭代函数计算的累积值,并将最后的累积值和由所有中间的累积值组成的列表一起返回。和 [mapAccum](https://ramda.cn/docs/#mapAccum) 类似,除了列表遍历顺序是从右往左的。
    const digits = ['1', '2', '3', '4'];
    const appender = (a, b) => [b + a, b + a];
    R.mapAccumRight(appender, 5, digits); //=> ['12345', ['12345', '2345', '345', '45']]
    <br />
    groupBy, groupWith,reduceBy, reduce, scan,reduceRight,reduceWhile,reduced,transduce
    // 对列表中的每个元素调用函数,根据函数返回结果进行分组。函数返回字符串作为相等性判断,返回的字符串作为存储对象的键,具有相同返回字符串的元素聚合为数组,作为该键的值。
    const byGrade = R.groupBy(function(student) {
    const score = student.score;
    return score < 65 ? 'F' :
    score < 70 ? 'D' :
    score < 80 ? 'C' :
    score < 90 ? 'B' : 'A';
    });
    const students = [{name: 'Abby', score: 84},
    {name: 'Eddy', score: 58},
    // ...
    {name: 'Jack', score: 69}];
    byGrade(students);
    // {
    // 'A': [{name: 'Dianne', score: 99}],
    // 'B': [{name: 'Abby', score: 84}]
    // // ...,
    // 'F': [{name: 'Eddy', score: 58}]
    // }
    <br />
    // 通过给定的对比函数,将列表按顺序分割成多组子列表。
    R.groupWith(R.equals, [0, 1, 1, 2, 3, 5, 8, 13, 21])
    //=> [[0], [1, 1], [2], [3], [5], [8], [13], [21]]
    <br />
    R.groupWith((a, b) => a + 1 === b, [0, 1, 1, 2, 3, 5, 8, 13, 21])
    //=> [[0, 1], [1, 2, 3], [5], [8], [13], [21]]
    <br />
    R.groupWith((a, b) => a % 2 === b % 2, [0, 1, 1, 2, 3, 5, 8, 13, 21])
    //=> [[0], [1, 1], [2], [3, 5], [8], [13, 21]]
    <br />
    R.groupWith(R.eqBy(isVowel), 'aestiou')
    //=> ['ae', 'st', 'iou']
    <br />
    // 首先对列表中的每个元素调用函数 keyFn ,根据 keyFn 返回的字符串对列表元素进行分组。然后调用 reducer 函数 valueFn,对组内的元素进行折叠操作。
    该函数相当于更通用的 [groupBy](https://ramda.cn/docs/#groupBy) 函数。
    const groupNames = (acc, {name}) => acc.concat(name)
    const toGrade = ({score}) =>
    score < 65 ? 'F' :
    score < 70 ? 'D' :
    score < 80 ? 'C' :
    score < 90 ? 'B' : 'A'
    <br />
    var students = [
    {name: 'Abby', score: 83},
    {name: 'Bart', score: 62},
    {name: 'Curt', score: 88},
    {name: 'Dora', score: 92},
    ]
    <br />
    reduceBy(groupNames, [], toGrade, students)
    //=> {"A": ["Dora"], "B": ["Abby", "Curt"], "F": ["Bart"]}
    <br />
    // 遍历列表,相继调用二元迭代函数(参数为累积值和从数组中取出的当前元素),将本次迭代结果作为下次迭代的累积值。返回最终累积值。
    R.reduce(R.subtract, 0, [1, 2, 3, 4]) // => ((((0 - 1) - 2) - 3) - 4) = -10
    <br />
    // Scan 与 [reduce](https://ramda.cn/docs/#reduce) 类似,但会将每次迭代计算的累积值记录下来,组成一个列表返回。
    const numbers = [1, 2, 3, 4];
    const factorials = R.scan(R.multiply, 1, numbers); //=> [1, 1, 2, 6, 24]
    <br />
    // 遍历列表,相继调用二元迭代函数(参数为累积值和从数组中取出的当前元素),将本次迭代结果作为下次迭代的累积值。返回最终累积值。
    R.reduceRight(R.subtract, 0, [1, 2, 3, 4]) // => (1 - (2 - (3 - (4 - 0)))) = -2
    <br />
    // 与 [reduce](https://ramda.cn/docs/#reduce) 类似, reduceWhile 会遍历列表,相继调用二元迭代函数,并返回最终累积值。reduceWhile 在每次调用迭代函数前,先使用 predicate 进行判断,如果 predicate 返回 false ,则提前终止遍历操作,并返回当前累积值。
    const isOdd = (acc, x) => x % 2 === 1;
    const xs = [1, 3, 5, 60, 777, 800];
    R.reduceWhile(isOdd, R.add, 0, xs); //=> 9
    <br />
    const ys = [2, 4, 6]
    R.reduceWhile(isOdd, R.add, 111, ys); //=> 111
    <br />
    // 返回一个封装的值,该值代表 reduce 或 transduce 操作的最终结果。
    R.reduce(
    (acc, item) => item > 3 ? R.reduced(acc) : acc.concat(item),
    [],
    [1, 2, 3, 4, 5]) // [1, 2, 3]
    <br />
    // 用 iterator function 初始化 transducer ,生成一个 transformed iterator function。然后顺次遍历列表,对每个列表元素先进行转换,然后与累积值进行归约,返回值作为下一轮迭代的累积值。最终返回与初始累积值类型相同的一个累积值。
    const numbers = [1, 2, 3, 4];
    const transducer = R.compose(R.map(R.add(1)), R.take(2));
    R.transduce(transducer, R.flip(R.append), [], numbers); //=> [2, 3]
    <br />
    const isOdd = (x) => x % 2 === 1;
    const firstOddTransducer = R.compose(R.filter(isOdd), R.take(1));
    R.transduce(firstOddTransducer, R.flip(R.append), [], R.range(0, 100)); //=> [1]
    into, unfold
    // accumulator 的类型可以是:array、string、object 或者 transformer 。如果 accumulator 类型是 array 或 string,则迭代元素将被添加到数组或连接到字符串上;如果是对象,迭代元素将会被直接合并;如果是二元素数组,迭代元素会以键值对形式进行合并。
    const numbers = [1, 2, 3, 4];
    const transducer = R.compose(R.map(R.add(1)), R.take(2));
    <br />
    R.into([], transducer, numbers); //=> [2, 3]
    <br />
    const intoArray = R.into([]);
    intoArray(transducer, numbers); //=> [2, 3]
    <br />
    // 通过一个种子值( seed )创建一个列表。unfold 接受一个迭代函数:该函数或者返回 false 停止迭代,或者返回一个长度为 2 的数组:数组首个元素添加到结果列表,第二个元素作为种子值传给下一轮迭代使用。
    const f = n => n > 50 ? false : [-n, n + 10];
    R.unfold(f, 10); //=> [-10, -20, -30, -40, -50]
    filter,reject,partition,innerJoin,
    // 使用 predicate 遍历传入的 Filterable,返回满足 predicate 的所有元素的新的 Filterable。新 Filterable 与原先的类型相同。Filterable 类型包括 plain object 或者任何带有 filter 方法的类型,如 Array 。
    const isEven = n => n % 2 === 0;
    R.filter(isEven, [1, 2, 3, 4]); //=> [2, 4]
    R.filter(isEven, {a: 1, b: 2, c: 3, d: 4}); //=> {b: 2, d: 4}
    <br />
    // [filter](https://ramda.cn/docs/#filter) 的补操作。返回结果为 [R.filter](https://ramda.cn/docs/#filter) 操作结果的补集。
    const isOdd = (n) => n % 2 === 1;
    R.reject(isOdd, [1, 2, 3, 4]); //=> [2, 4]
    R.reject(isOdd, {a: 1, b: 2, c: 3, d: 4}); //=> {b: 2, d: 4}
    <br />
    // 通过 predicate 将列表或 "Filterable" (可过滤的)对象分成两部分,分别为满足 predicate 的元素和不满足 predicate 的元素。元素类型保持不变。Filterable 类型包括 plain object 或者任何带有 filter 方法的类型,如 Array 。
    R.partition(R.includes('s'), ['sss', 'ttt', 'foo', 'bars']);
    // => [ [ 'sss', 'bars' ], [ 'ttt', 'foo' ] ]
    R.partition(R.includes('s'), { a: 'sss', b: 'ttt', foo: 'bars' });
    // => [ { a: 'sss', foo: 'bars' }, { b: 'ttt' } ]
    <br />
    // 接受一个 predicate pred 、列表 xs 和 ys ,返回列表 xs'。依次取出 xs 中的元素,若通过 pred 判断等于 ys 中的一个或多个元素,则放入 xs' 。pred 必须为二元函数,两个参数分别来自于对应两个列表中的元素。
    R.innerJoin(
    (record, id) => record.id === id,
    [{id: 824, name: 'Richie Furay'},
    {id: 956, name: 'Dewey Martin'},
    {id: 313, name: 'Bruce Palmer'},
    {id: 456, name: 'Stephen Stills'},
    {id: 177, name: 'Neil Young'}],
    [177, 456, 999]
    );
    //=> [{id: 456, name: 'Stephen Stills'}, {id: 177, name: 'Neil Young'}]

    • 改变对象,函数转化

    adjust, update,insert, insertAll,remove,intersperse,move
    // 将数组中指定索引处的值替换为经函数变换的值
    R.adjust(1, R.toUpper, ['a', 'b', 'c', 'd']); //=> ['a', 'B', 'c', 'd']
    <br />
    R.update(1, '_', ['a', 'b', 'c']); //=> ['a', '_', 'c']
    <br />
    // 将元素插入到 list 指定索引处。注意,该函数是非破坏性的:返回处理后列表的拷贝。函数运行过程中不会破坏任何列表。
    R.insert(2, 'x', [1,2,3,4]); //=> [1,2,'x',3,4]
    <br />
    //将子 list 插入到 list 指定索引处。注意,该函数是非破坏性的:返回处理后列表的拷贝。函数运行过程中不会破坏任何列表。
    R.insertAll(2, ['x','y','z'], [1,2,3,4]); //=> [1,2,'x','y','z',3,4]
    <br />
    //删除列表中从 start 开始的 count 个元素。注意,该操作是非破坏性的:不改变原列表,返回处理后列表的拷贝。
    R.remove(2, 3, [1,2,3,4,5,6,7,8]); //=> [1,2,6,7,8]
    <br />
    // 在列表的元素之间插入分割元素。
    R.intersperse('a', ['b', 'n', 'n', 's']); //=> ['b', 'a', 'n', 'a', 'n', 'a', 's']
    <br />
    // 将列表中 from 索引处的元素移动到索引 to 处。
    R.move(0, 2, ['a', 'b', 'c', 'd', 'e', 'f']); //=> ['b', 'c', 'a', 'd', 'e', 'f']
    R.move(-1, 0, ['a', 'b', 'c', 'd', 'e', 'f']); //=> ['f', 'a', 'b', 'c', 'd', 'e'] list rotation
    merge,mergeAll, mergeRight, mergeDeepRight, mergeLeft, mergeDeepLeft,mergeWith, mergeDeepWith, mergeWithKey, mergeDeepWithKey
    // 合并两个对象的自身属性(不包括 prototype 属性)。如果某个 key 在两个对象中都存在,使用后一个对象对应的属性值。
    R.merge({ 'name': 'fred', 'age': 10 }, { 'age': 40 });
    //=> { 'name': 'fred', 'age': 40 }
    const withDefaults = R.merge({x: 0, y: 0});
    withDefaults({y: 2}); //=> {x: 0, y: 2}
    <br />
    //将对象类型列表合并为一个对象。
    R.mergeAll([{foo:1},{bar:2},{baz:3}]); //=> {foo:1,bar:2,baz:3}
    R.mergeAll([{foo:1},{foo:2},{bar:2}]); //=> {foo:2,bar:2}
    <br />
    // 合并两个对象的自身属性(不包括 prototype 属性)。如果某个 key 在两个对象中都存在,使用后一个对象对应的属性值。
    R.mergeRight({ 'name': 'fred', 'age': 10 }, { 'age': 40 });
    //=> { 'name': 'fred', 'age': 40 }
    const withDefaults = R.mergeRight({x: 0, y: 0});
    withDefaults({y: 2}); //=> {x: 0, y: 2}
    <br />
    // 合并两个对象的自身属性(不包括 prototype 属性)。如果某个 key 在两个对象中都存在:并且两个值都是对象,则继续递归合并这两个值;否则,采用第二个对象的值。
    R.mergeDeepRight(
    { name: 'fred', age: 10, contact: { email: 'moo@example.com' }},
    { age: 40, contact: { email: 'baa@example.com' }});
    //=> { name: 'fred', age: 40, contact: { email: 'baa@example.com' }}
    <br />
    // 合并两个对象的自身属性(不包括 prototype 属性)。如果某个 key 在两个对象中都存在,使用前一个对象对应的属性值。
    R.mergeLeft({ 'age': 40 }, { 'name': 'fred', 'age': 10 });
    //=> { 'name': 'fred', 'age': 40 }
    const resetToDefault = R.mergeLeft({x: 0});
    resetToDefault({x: 5, y: 2}); //=> {x: 0, y: 2}
    <br />
    // 合并两个对象的自身属性(不包括 prototype 属性)。如果某个 key 在两个对象中都存在:并且两个值都是对象,则继续递归合并这两个值;否则,采用第一个对象的值。
    R.mergeDeepLeft(
    { name: 'fred', age: 10, contact: { email: 'moo@example.com' }},
    { age: 40, contact: { email: 'baa@example.com' }});
    //=> { name: 'fred', age: 10, contact: { email: 'moo@example.com' }}
    <br />
    // 使用给定的两个对象自身属性(不包括 prototype 属性)来创建一个新对象。如果某个 key 在两个对象中都存在,则使用给定的函数对每个对象该 key 对应的 value 进行处理,处理结果作为新对象该 key 对应的值。
    R.mergeWith(R.concat,
    { a: true, values: [10, 20] },
    { b: true, values: [15, 35] });
    //=> { a: true, b: true, values: [10, 20, 15, 35] }
    <br />
    // 合并两个对象的自身属性(不包括 prototype 属性)。如果某个 key 在两个对象中都存在:并且两个关联的值都是对象,则继续递归合并这两个值;否则,使用给定函数对两个值进行处理,并将返回值作为该 key 的新值。
    R.mergeDeepWith(R.concat,
    { a: true, c: { values: [10, 20] }},
    { b: true, c: { values: [15, 35] }});
    //=> { a: true, b: true, c: { values: [10, 20, 15, 35] }}
    <br />
    // 使用给定的两个对象自身属性(不包括 prototype 属性)来创建一个新对象。如果某个 key 在两个对象中都存在,则使用给定的函数对该 key 和每个对象该 key 对应的 value 进行处理,处理结果作为新对象该 key 对应的值。
    let concatValues = (k, l, r) => k == 'values' ? R.concat(l, r) : r
    R.mergeWithKey(concatValues,
    { a: true, thing: 'foo', values: [10, 20] },
    { b: true, thing: 'bar', values: [15, 35] });
    //=> { a: true, b: true, thing: 'bar', values: [10, 20, 15, 35] }
    <br />
    // 合并两个对象的自身属性(不包括 prototype 属性)。如果某个 key 在两个对象中都存在:并且两个关联的值都是对象,则继续递归合并这两个值;否则,使用给定函数对该 key 和对应的两个值进行处理,并将返回值作为该 key 的新值。
    let concatValues = (k, l, r) => k == 'values' ? R.concat(l, r) : r
    R.mergeDeepWithKey(concatValues,
    { a: true, c: { thing: 'foo', values: [10, 20] }},
    { b: true, c: { thing: 'bar', values: [15, 35] }});
    //=> { a: true, b: true, c: { thing: 'bar', values: [10, 20, 15, 35] }}
    applySpec
    //接受一个属性值为函数的对象,返回一个能生成相同结构对象的函数。返回的函数使用传入的参数调用对象的每个属性位对应的函数,来生成相应属性的值。
    const getMetrics = R.applySpec({
    sum: R.add,
    nested: { mul: R.multiply }
    });
    getMetrics(2, 4); // => { sum: 6, nested: { mul: 8 } }
    evolve
    // 递归地对 object 的属性进行变换,变换方式由 transformation 函数定义。所有非原始类型属性都通过引用来复制。
    const tomato = {firstName: ' Tomato ', data: {elapsed: 100, remaining: 1400}, id:123};
    const transformations = {
    firstName: R.trim,
    lastName: R.trim, // Will not get invoked.
    data: {elapsed: R.add(1), remaining: R.add(-1)}
    };
    R.evolve(transformations, tomato); //=> {firstName: 'Tomato', data: {elapsed: 101, remaining: 1399}, id:123}
    converge, useWith,ap,lift, liftN
    // 接受一个 converging 函数和一个分支函数列表,返回一个新函数。新函数的元数(参数个数)等于最长分支函数的元数。当被调用时,新函数接受参数,并将这些参数转发给每个分支函数;然后将每个分支函数的计算结果作为参数传递给 converging 函数,converging 函数的计算结果即新函数的返回值
    const average = R.converge(R.divide, [R.sum, R.length])
    average([1, 2, 3, 4, 5, 6, 7]) //=> 4
    <br />
    const strangeConcat = R.converge(R.concat, [R.toUpper, R.toLower])
    strangeConcat("Yodel") //=> "YODELyodel"
    <br />
    // 接受一个函数 fn 和一个 transformer 函数的列表,返回一个柯里化的新函数。当被调用时,新函数将每个参数转发给对应位置的 transformer 函数,然后将每个 transformer 函数的计算结果作为参数传递给 fn,fn 的计算结果即新函数的返回值。
    R.useWith(Math.pow, [R.identity, R.identity])(3, 4); //=> 81
    R.useWith(Math.pow, [R.identity, R.identity])(3)(4); //=> 81
    R.useWith(Math.pow, [R.dec, R.inc])(3, 4); //=> 32
    R.useWith(Math.pow, [R.dec, R.inc])(3)(4); //=> 32
    <br />
    // ap 将函数列表作用于值列表上。
    R.ap([R.multiply(2), R.add(3)], [1,2,3]); //=> [2, 4, 6, 4, 5, 6]
    R.ap([R.concat('tasty '), R.toUpper], ['pizza', 'salad']); //=> ["tasty pizza", "tasty salad", "PIZZA", "SALAD"]
    R.ap(R.concat, R.toUpper)('Ramda') //=> 'RamdaRAMDA'
    <br />
    // 提升一个多元函数,使之能映射到列表、函数或其他符合 [FantasyLand Apply spec](https://github.com/fantasyland/fantasy-land#apply) 规范的对象上
    const madd3 = R.lift((a, b, c) => a + b + c);
    madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
    const madd5 = R.lift((a, b, c, d, e) => a + b + c + d + e);
    madd5([1,2], [3], [4, 5], [6], [7, 8]); //=> [21, 22, 22, 23, 22, 23, 23, 24]
    <br />
    //将一个函数提升为指定元数的函数,使之能映射到多个列表、函数或其他符合 [FantasyLand Apply spec](https://github.com/fantasyland/fantasy-land#apply) 规范的对象上。
    const madd3 = R.liftN(3, (...args) => R.sum(args));
    madd3([1,2,3], [1,2,3], [1]); //=> [3, 4, 5, 4, 5, 6, 5, 6, 7]
    juxt
    const getRange = R.juxt([Math.min, Math.max]);
    getRange(3, 4, 9, -3); //=> [-3, 9]
    assoc, assocPath,dissoc, dissocPath ,omit, pick, pickBy,pickAll,drop, dropLast, dropLastWhile, dropWhile ,take, takeLast, takeLastWhile, takeWhile,pluck,project
    // 浅复制对象,然后设置或覆盖对象的指定属性
    R.assoc('c', 3, {a: 1, b: 2}); //=> {a: 1, b: 2, c: 3}
    <br />
    R.assocPath(['a', 'b', 'c'], 42, {a: {b: {c: 0}}}); //=> {a: {b: {c: 42}}}
    R.assocPath(['a', 'b', 'c'], 42, {a: 5}); //=> {a: {b: {c: 42}}}
    <br />
    R.dissoc('b', {a: 1, b: 2, c: 3}); //=> {a: 1, c: 3}
    <br />
    R.dissocPath(['a', 'b', 'c'], {a: {b: {c: 42}}}); //=> {a: {b: {}}}
    <br />
    R.omit(['a', 'd'], {a: 1, b: 2, c: 3, d: 4}); //=> {b: 2, c: 3}
    <br />
    // 返回对象的部分拷贝,其中仅包含指定键对应的属性。如果某个键不存在,则忽略该属性。
    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}
    <br />
    // 返回对象的部分拷贝,其中仅包含 key 满足 predicate 的属性。
    const isUpperCase = (val, key) => key.toUpperCase() === key;
    R.pickBy(isUpperCase, {a: 1, b: 2, A: 3, B: 4}); //=> {A: 3, B: 4}
    <br />
    //与 pick 类似,但 pickAll 会将不存在的属性以 key: undefined 键值对的形式返回
    R.pickAll(['a', 'd'], {a: 1, b: 2, c: 3, d: 4}); //=> {a: 1, d: 4}
    R.pickAll(['a', 'e', 'f'], {a: 1, b: 2, c: 3, d: 4}); //=> {a: 1, e: undefined, f: undefined}
    <br />
    // 删除给定 list,string 或者 transducer/transformer(或者具有 drop 方法的对象)的前 n 个元素。
    R.drop(1, ['foo', 'bar', 'baz']); //=> ['bar', 'baz']
    R.drop(2, ['foo', 'bar', 'baz']); //=> ['baz']
    R.drop(3, ['foo', 'bar', 'baz']); //=> []
    R.drop(3, 'ramda'); //=> 'da'
    <br />
    // 删除 "list" 末尾的 n 个元素。
    R.dropLast(1, ['foo', 'bar', 'baz']); //=> ['foo', 'bar']
    R.dropLast(2, ['foo', 'bar', 'baz']); //=> ['foo']
    R.dropLast(3, ['foo', 'bar', 'baz']); //=> []
    R.dropLast(3, 'ramda'); //=> 'ra'
    <br />
    // 对 list 从后向前一直删除满足 predicate 的尾部元素,直到遇到第一个 falsy 值,此时停止删除操作。
    const lteThree = x => x <= 3;
    R.dropLastWhile(lteThree, [1, 2, 3, 4, 3, 2, 1]); //=> [1, 2, 3, 4]
    R.dropLastWhile(x => x !== 'd' , 'Ramda'); //=> 'Ramd'
    <br />
    // 对 list 从前向后删除满足 predicate 的头部元素,直到遇到第一个 falsy 值。
    const lteTwo = x => x <= 2;
    R.dropWhile(lteTwo, [1, 2, 3, 4, 3, 2, 1]); //=> [3, 4, 3, 2, 1]
    R.dropWhile(x => x !== 'd' , 'Ramda'); //=> 'da'
    <br />
    // 返回列表的前 n 个元素、字符串的前n个字符或者用作 transducer/transform(或者调用对象的 take 方法)
    R.take(1, ['foo', 'bar', 'baz']); //=> ['foo']
    R.take(2, ['foo', 'bar', 'baz']); //=> ['foo', 'bar']
    R.take(3, ['foo', 'bar', 'baz']); //=> ['foo', 'bar', 'baz']
    R.take(3, 'ramda'); //=> 'ram'
    <br />
    // 返回列表的后 n 个元素。如果 n > list.length,则返回 list.length 个元素。
    R.takeLast(1, ['foo', 'bar', 'baz']); //=> ['baz']
    R.takeLast(2, ['foo', 'bar', 'baz']); //=> ['bar', 'baz']
    R.takeLast(3, ['foo', 'bar', 'baz']); //=> ['foo', 'bar', 'baz']
    R.takeLast(3, 'ramda'); //=> 'mda'
    <br />
    // 从后往前取出列表元素,直到遇到首个不满足 predicate 的元素为止。取出的元素中不包含首个不满足 predicate 的元素。
    const isNotOne = x => x !== 1;
    R.takeLastWhile(isNotOne, [1, 2, 3, 4]); //=> [2, 3, 4]
    R.takeLastWhile(x => x !== 'R' , 'Ramda'); //=> 'amda'
    <br />
    // 从前往后取出列表元素,直到遇到首个不满足 predicate 的元素为止。取出的元素中不包含首个不满足 predicate 的元素。
    const isNotFour = x => x !== 4;
    R.takeWhile(isNotFour, [1, 2, 3, 4, 3, 2, 1]); //=> [1, 2, 3]
    R.takeWhile(x => x !== 'd' , 'Ramda'); //=> 'Ram'
    <br />
    // 从列表内的每个对象元素中取出特定名称的属性,组成一个新的列表。
    var getAges = R.pluck('age');
    getAges([{name: 'fred', age: 29}, {name: 'wilma', age: 27}]); //=> [29, 27]
    R.pluck(0, [[1, 2], [3, 4]]); //=> [1, 3]
    R.pluck('val', {a: {val: 3}, b: {val: 5}}); //=> {a: 3, b: 5}
    <br />
    // 模拟 SQL 中的 select 语句。
    const abby = {name: 'Abby', age: 7, hair: 'blond', grade: 2};
    const fred = {name: 'Fred', age: 12, hair: 'brown', grade: 7};
    const kids = [abby, fred];
    R.project(['name', 'grade'], kids); //=> [{name: 'Abby', grade: 2}, {name: 'Fred', grade: 7}]
    lens, lensIndex, lensProp
    //返回封装了给定 getter 和 setter 方法的 lens 。 getter 和 setter 分别用于 “获取” 和 “设置” 焦点(lens 聚焦的值)。setter 不会改变原数据。
    const xLens = R.lens(R.prop('x'), R.assoc('x'));
    R.view(xLens, {x: 1, y: 2}); //=> 1
    R.set(xLens, 4, {x: 1, y: 2}); //=> {x: 4, y: 2}
    R.over(xLens, R.negate, {x: 1, y: 2}); //=> {x: -1, y: 2}
    <br />
    // 返回聚焦到指定索引的 lens。
    const headLens = R.lensIndex(0);
    R.view(headLens, ['a', 'b', 'c']); //=> 'a'
    R.set(headLens, 'x', ['a', 'b', 'c']); //=> ['x', 'b', 'c']
    R.over(headLens, R.toUpper, ['a', 'b', 'c']); //=> ['A', 'b', 'c']
    <br />
    //返回聚焦到指定属性的 lens。
    const xLens = R.lensProp('x');
    R.view(xLens, {x: 1, y: 2}); //=> 1
    R.set(xLens, 4, {x: 1, y: 2}); //=> {x: 4, y: 2}
    R.over(xLens, R.negate, {x: 1, y: 2}); //=> {x: -1, y: 2}
    <br />
    // 返回聚焦到指定路径的 lens。
    const xHeadYLens = R.lensPath(['x', 0, 'y']);
    R.view(xHeadYLens, {x: [{y: 2, z: 3}, {y: 4, z: 5}]});
    //=> 2
    R.set(xHeadYLens, 1, {x: [{y: 2, z: 3}, {y: 4, z: 5}]});
    //=> {x: [{y: 1, z: 3}, {y: 4, z: 5}]}
    R.over(xHeadYLens, R.negate, {x: [{y: 2, z: 3}, {y: 4, z: 5}]});
    //=> {x: [{y: -2, z: 3}, {y: 4, z: 5}]}
    view,set,over,prop, props, propsOr,propIs, propEq ,propSatisfies,path, paths,pathOr,pathEq, pathSatisfies
    // 返回数据结构中,lens 聚焦的部分。lens 的焦点决定了数据结构中的哪部分是可见的。
    const xLens = R.lensProp('x');
    R.view(xLens, {x: 1, y: 2}); //=> 1
    R.view(xLens, {x: 4, y: 2}); //=> 4
    <br />
    //通过 lens 对数据结构聚焦的部分进行设置
    const xLens = R.lensProp('x');
    R.set(xLens, 4, {x: 1, y: 2}); //=> {x: 4, y: 2}
    R.set(xLens, 8, {x: 1, y: 2}); //=> {x: 8, y: 2}
    <br />
    // 对数据结构中被 lens 聚焦的部分进行函数变换
    const headLens = R.lensIndex(0);
    R.over(headLens, R.toUpper, ['foo', 'bar', 'baz']); //=> ['FOO', 'bar', 'baz']
    <br />
    // 取出对象中指定属性的值。如果不存在,则返回 undefined。
    R.prop('x', {x: 100}); //=> 100
    R.prop('x', {}); //=> undefined
    R.prop(0, [100]); //=> 100
    R.compose(R.inc, R.prop('x'))({ x: 3 }) //=> 4
    <br />
    // 返回 prop 的数组:输入为 keys 数组,输出为对应的 values 数组。values 数组的顺序与 keys 的相同。
    R.props(['x', 'y'], {x: 1, y: 2}); //=> [1, 2]
    R.props(['c', 'a', 'b'], {b: 2, a: 1}); //=> [undefined, 1, 2]
    const fullName = R.compose(R.join(' '), R.props(['first', 'last']));
    fullName({last: 'Bullet-Tooth', age: 33, first: 'Tony'}); //=> 'Tony Bullet-Tooth'
    <br />
    // 对于给定的非空对象,如果指定属性存在,则返回该属性值;否则返回给定的默认值。
    const alice = {
    name: 'ALICE',
    age: 101
    };
    const favorite = R.prop('favoriteLibrary');
    const favoriteWithDefault = R.propOr('Ramda', 'favoriteLibrary');
    favorite(alice); //=> undefined
    favoriteWithDefault(alice); //=> 'Ramda'
    <br />
    // 判断指定对象的属性是否为给定的数据类型,是则返回 true ;否则返回 false 。
    R.propIs(Number, 'x', {x: 1, y: 2}); //=> true
    R.propIs(Number, 'x', {x: 'foo'}); //=> false
    R.propIs(Number, 'x', {}); //=> false
    <br />
    // 如果指定对象属性与给定的值相等,则返回 true ;否则返回 false 。通过 [R.equals](https://ramda.cn/docs/#equals) 函数进行相等性判断。可以使用 [R.whereEq](https://ramda.cn/docs/#whereEq) 进行多个属性的相等性判断。
    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]
    <br />
    // 如果指定的对象属性满足 predicate,返回 true;否则返回 false。可以使用 [R.where](https://ramda.cn/docs/#where) 进行多个属性的判断。
    R.propSatisfies(x => x > 0, 'x', {x: 1, y: 2}); //=> true
    <br />
    // 取出给定路径上的值。
    R.path(['a', 'b'], {a: {b: 2}}); //=> 2
    R.path(['a', 'b'], {c: {b: 2}}); //=> undefined
    R.path(['a', 'b', 0], {a: {b: [1, 2, 3]}}); //=> 1
    R.path(['a', 'b', -2], {a: {b: [1, 2, 3]}}); //=> 2
    <br />
    // 提取对象中指定路径数组(paths)上的对应的值(values)
    R.paths([['a', 'b'], ['p', 0, 'q']], {a: {b: 2}, p: [{q: 3}]}); //=> [2, 3]
    R.paths([['a', 'b'], ['p', 'r']], {a: {b: 2}, p: [{q: 3}]}); //=> [2, undefined]
    <br />
    //如果非空对象在给定路径上存在值,则将该值返回;否则返回给定的默认值。
    R.pathOr('N/A', ['a', 'b'], {a: {b: 2}}); //=> 2
    R.pathOr('N/A', ['a', 'b'], {c: {b: 2}}); //=> "N/A"
    <br />
    // 判断对象的嵌套路径上是否为给定的值,通过 [R.equals](https://ramda.cn/docs/#equals) 函数进行相等性判断。常用于列表过滤。
    const user1 = { address: { zipCode: 90210 } };
    const user2 = { address: { zipCode: 55555 } };
    const user3 = { name: 'Bob' };
    const users = [ user1, user2, user3 ];
    const isFamous = R.pathEq(['address', 'zipCode'], 90210);
    R.filter(isFamous, users); //=> [ user1 ]
    <br />
    //如果对象的给定路径上的属性满足 predicate,返回 ture;否则返回 false。
    R.pathSatisfies(y => y > 0, ['x', 'y'], {x: {y: 2}}); //=> true
    R.pathSatisfies(R.is(Object), [], {x: {y: 2}}); //=> true
    <br />
    indexBy,invert, invertObj
    // 通过生成键的函数,将元素为对象的 list 转换为以生成的键为索引的新对象。注意,如果 list 中多个对象元素生成相同的键,以最后一个对象元素作为该键的值。
    const list = [{id: 'xyz', title: 'A'}, {id: 'abc', title: 'B'}];
    R.indexBy(R.prop('id'), list);
    //=> {abc: {id: 'abc', title: 'B'}, xyz: {id: 'xyz', title: 'A'}}
    <br />
    // 与 [R.invertObj](https://ramda.cn/docs/#invertObj) 类似,但会将值放入数组中,来处理一个键对应多个值的情况。
    const raceResultsByFirstName = {
    first: 'alice',
    second: 'jake',
    third: 'alice',
    };
    R.invert(raceResultsByFirstName);
    //=> { 'alice': ['first', 'third'], 'jake':['second'] }
    <br />
    // 将对象的键、值交换位置:值作为键,对应的键作为值。交换后的键会被强制转换为字符串。注意,如果原对象同一值对应多个键,采用最后遍历到的键。
    const raceResults = {
    first: 'alice',
    second: 'jake'
    };
    R.invertObj(raceResults);
    //=> { 'alice': 'first', 'jake':'second' }
    const raceResults = ['alice', 'jake'];
    R.invertObj(raceResults);
    //=> { 'alice': '0', 'jake':'1' }
    fromPairs,toPairs,pair,objOf,of
    // 由一系列 “键值对” 创建一个 object。如果某个键出现多次,选取最右侧的键值对。
    R.fromPairs([['a', 1], ['b', 2], ['c', 3]]); //=> {a: 1, b: 2, c: 3}
    <br />
    // 将一个对象的属性转换成键、值二元组类型的数组,只处理对象自身的属性。注意:不同 JS 运行环境输出数组的顺序可能不一致。
    R.toPairs({a: 1, b: 2, c: 3}); //=> [['a', 1], ['b', 2], ['c', 3]]
    <br />
    // 将一个对象的属性转换成键、值二元组类型的数组,包括原型链上的属性。注意,不同 JS 运行环境输出数组的顺序可能不一致。
    const F = function() { this.x = 'X'; };
    F.prototype.y = 'Y';
    const f = new F();
    R.toPairsIn(f); //=> [['x','X'], ['y','Y']]
    <br />
    // 接收两个参数,fst 和 snd,返回数组 [fst, snd]。
    R.pair('foo', 'bar'); //=> ['foo', 'bar']
    <br />
    // 创建一个包含单个键值对的对象。
    const matchPhrases = R.compose(
    R.objOf('must'),
    R.map(R.objOf('match_phrase'))
    );
    matchPhrases(['foo', 'bar', 'baz']); //=> {must: [{match_phrase: 'foo'}, {match_phrase: 'bar'}, {match_phrase: 'baz'}]}
    <br />
    // 将给定值作为元素,封装成单元素数组。
    R.of(null); //=> [null]
    R.of([42]); //=> [[42]]

    • 数组或字符串方法

    aperture, splitAt,splitEvery, splitWhen
    // 返回一个新列表,列表中的元素为由原列表相邻元素组成的 n 元组。如果 n 大于列表的长度,则返回空列表。
    R.aperture(2, [1, 2, 3, 4, 5]); //=> [[1, 2], [2, 3], [3, 4], [4, 5]]
    R.aperture(3, [1, 2, 3, 4, 5]); //=> [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
    R.aperture(7, [1, 2, 3, 4, 5]); //=> []
    <br />
    // 在指定的索引处拆分列表或者字符串。
    R.splitAt(1, [1, 2, 3]); //=> [[1], [2, 3]]
    R.splitAt(5, 'hello world'); //=> ['hello', ' world']
    R.splitAt(-1, 'foobar'); //=> ['fooba', 'r']
    <br />
    // 将列表拆分成指定长度的子列表集。
    R.splitEvery(3, [1, 2, 3, 4, 5, 6, 7]); //=> [[1, 2, 3], [4, 5, 6], [7]]
    R.splitEvery(3, 'foobarbaz'); //=> ['foo', 'bar', 'baz']
    <br />
    // 查找列表中首个满足 predicate 的元素,在该处将列表拆分为两部分。首个满足 predicate 的元素包含在后一部分
    R.splitWhen(R.equals(2), [1, 2, 3, 1, 2, 3]); //=> [[1], [2, 3, 1, 2, 3]]
    append, prepend
    R.append('tests', ['write', 'more']); //=> ['write', 'more', 'tests']
    <br />
    R.prepend('fee', ['fi', 'fo', 'fum']); //=> ['fee', 'fi', 'fo', 'fum']
    countBy
    // 根据给定函数提供的统计规则对列表中的元素进行分类计数。返回一个对象,其键值对为:fn 根据列表元素生成键,列表中通过 fn 映射为对应键的元素的个数作为值。注意,由于 JavaScript 对象的实现方式,所有键都被强制转换为字符串。
    const numbers = [1.0, 1.1, 1.2, 2.0, 3.0, 2.2];
    R.countBy(Math.floor)(numbers); //=> {'1': 3, '2': 2, '3': 1}
    <br />
    const letters = ['a', 'b', 'A', 'a', 'B', 'c'];
    R.countBy(R.toLower)(letters); //=> {'a': 3, 'b': 2, 'c': 1}
    difference, differenceWith, intersection,symmetricDifference, symmetricDifferenceWith,without,dropRepeats, uniq, uniqBy, uniqWith,
    union, unionWith,dropRepeatsWith
    // 求差集。求第一个列表中,未包含在第二个列表中的任一元素的集合。对象和数组比较数值相等,而非引用相等。
    R.difference([1,2,3,4], [7,6,5,4,3]); //=> [1,2]
    R.difference([7,6,5,4,3], [1,2,3,4]); //=> [7,6,5]
    R.difference([{a: 1}, {b: 2}], [{a: 1}, {c: 3}]) //=> [{b: 2}]
    <br />
    // 求第一个列表中未包含在第二个列表中的所有元素的集合(集合中没有重复元素)。两列表中的元素通过 predicate 判断相应元素是否同时 “包含在” 两列表中。
    const cmp = (x, y) => x.a === y.a;
    const l1 = [{a: 1}, {a: 2}, {a: 3}];
    const l2 = [{a: 3}, {a: 4}];
    R.differenceWith(cmp, l1, l2); //=> [{a: 1}, {a: 2}]
    <br />
    //取出两个 list 中相同的元素组成的 set (集合:没有重复元素)
    R.intersection([1,2,3,4], [7,6,5,4,3]); //=> [4, 3]
    <br />
    // 求对称差集。所有不属于两列表交集元素的集合,其元素在且仅在给定列表中的一个里面出现。
    R.symmetricDifference([1,2,3,4], [7,6,5,4,3]); //=> [1,2,7,6,5]
    R.symmetricDifference([7,6,5,4,3], [1,2,3,4]); //=> [7,6,5,1,2]
    <br />
    // 求对称差集。所有不属于两列表交集元素的集合。交集的元素由条件函数的返回值决定。
    const eqA = R.eqBy(R.prop('a'));
    const l1 = [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
    const l2 = [{a: 3}, {a: 4}, {a: 5}, {a: 6}];
    R.symmetricDifferenceWith(eqA, l1, l2); //=> [{a: 1}, {a: 2}, {a: 5}, {a: 6}]
    <br />
    // 求第二个列表中,未包含在第一个列表中的任一元素的集合。通过 [R.equals](https://ramda.cn/docs/#equals) 函数进行相等性判断。
    R.without([1, 2], [1, 2, 1, 3, 4]); //=> [3, 4]
    <br />
    // 返回一个没有连续重复元素的 list。通过 [R.equals](https://ramda.cn/docs/#equls) 函数进行相等性判断。
    R.dropRepeats([1, 1, 1, 2, 3, 4, 4, 2, 2]); //=> [1, 2, 3, 4, 2]
    <br />
    // 列表去重操作。返回无重复元素的列表。通过 [R.equals](https://ramda.cn/docs/#equals) 函数进行相等性判断。
    R.uniq([1, 1, 2, 1]); //=> [1, 2]
    R.uniq([1, '1']); //=> [1, '1']
    R.uniq([[42], [42]]); //=> [[42]]
    <br />
    // 返回无重复元素的列表。元素通过给定的函数的返回值以及 [R.equals](https://ramda.cn/docs/#equals) 进行相同性判断。如果给定的函数返回值相同,保留第一个元素。
    R.uniqBy(Math.abs, [-1, -5, 2, 10, 1, 2]); //=> [-1, -5, 2, 10]
    <br />
    // 返回无重复元素的列表。元素通过 predicate 进行相同性判断。如果通过 predicate 判断两元素相同,保留第一个元素。
    const strEq = R.eqBy(String);
    R.uniqWith(strEq)([1, '1', 2, 1]); //=> [1, 2]
    R.uniqWith(strEq)([{}, {}]); //=> [{}]
    R.uniqWith(strEq)([1, '1', 1]); //=> [1]
    R.uniqWith(strEq)(['1', 1, 1]); //=> ['1']
    <br />
    // 集合并运算,合并两个列表为新列表(新列表中无重复元素)。
    R.union([1, 2, 3], [2, 3, 4]); //=> [1, 2, 3, 4]
    <br />
    // 集合并运算,合并两个列表为新列表(新列表中无重复元素)。由 predicate 的返回值决定两元素是否重复。
    const l1 = [{a: 1}, {a: 2}];
    const l2 = [{a: 1}, {a: 4}];
    R.unionWith(R.eqBy(R.prop('a')), l1, l2); //=> [{a: 1}, {a: 2}, {a: 4}]
    <br />
    // 返回一个没有连续重复元素的 list。首个参数提供的 predicate 用于检测 list 中相邻的两个元素是否相等。一系列相等元素中的首个元素会被保留。
    const l = [1, -1, 1, 3, 4, -4, -4, -5, 5, 3, 3];
    R.dropRepeatsWith(R.eqBy(Math.abs), l); //=> [1, 3, 4, -5, 3]
    find,findIndex,findLast,findLastIndex
    // 查找并返回 list 中首个满足 predicate 的元素;如果未找到满足条件的元素,则返回 undefined
    const xs = [{a: 1}, {a: 2}, {a: 3}];
    R.find(R.propEq('a', 2))(xs); //=> {a: 2}
    R.find(R.propEq('a', 4))(xs); //=> undefined
    <br />
    // 查找并返回 list 中首个满足 predicate 的元素的索引;如果未找到满足条件的元素,则返回 -1 。
    const xs = [{a: 1}, {a: 2}, {a: 3}];
    R.findIndex(R.propEq('a', 2))(xs); //=> 1
    R.findIndex(R.propEq('a', 4))(xs); //=> -1
    <br />
    // 查找并返回 list 中最后一个满足 predicate 的元素;如果未找到满足条件的元素,则返回 undefined 。
    const xs = [{a: 1, b: 0}, {a:1, b: 1}];
    R.findLast(R.propEq('a', 1))(xs); //=> {a: 1, b: 1}
    R.findLast(R.propEq('a', 4))(xs); //=> undefined
    <br />
    // 查找并返回 list 中最后一个满足 predicate 的元素的索引;如果未找到满足条件的元素,则返回 -1 。
    const xs = [{a: 1, b: 0}, {a:1, b: 1}];
    R.findLastIndex(R.propEq('a', 1))(xs); //=> 1
    R.findLastIndex(R.propEq('a', 4))(xs); //=> -1
    chain,flatten,unnest
    // chain 将函数映射到列表中每个元素,并将结果连接起来。 chain 在一些库中也称为 flatMap(先 map 再 flatten )。
    const duplicate = n => [n, n];
    R.chain(duplicate, [1, 2, 3]); //=> [1, 1, 2, 2, 3, 3]
    R.chain(R.append, R.head)([1, 2, 3]); //=> [1, 2, 3, 1]
    <br />
    // 获取 list 的所有元素(包含所有子数组中的元素),然后由这些元素组成一个新的数组。深度优先。
    R.flatten([1, 2, [3, 4], 5, [6, [7, 8, [9, [10, 11], 12]]]]);
    //=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
    <br />
    // R.chain(R.identity) 的简写, 对 [Chain](https://github.com/fantasyland/fantasy-land#chain) 类型的数据消除一层嵌套.
    R.unnest([1, [2], [[3]]]); //=> [1, 2, [3]]
    R.unnest([[1, 2], [3, 4], [5, 6]]); //=> [1, 2, 3, 4, 5, 6]
    indexOf,lastIndexOf
    // 返回给定元素在数组中首次出现时的索引值,如果数组中没有该元素,则返回 -1。通过 [R.equals](https://ramda.cn/docs/#equals) 函数进行相等性判断。
    R.indexOf(3, [1,2,3,4]); //=> 2
    R.indexOf(10, [1,2,3,4]); //=> -1
    <br />
    // 返回数组中某元素最后一次出现的位置,如果数组中不包含该项则返回 -1 。通过 [R.equals](https://ramda.cn/docs/#equals) 函数进行相等性判断
    R.lastIndexOf(3, [-1,3,3,0,1,2,3,4]); //=> 6
    R.lastIndexOf(10, [1,2,3,4]); //=> -1
    join,split, splitAt
    // 将列表中所有元素通过 分隔符 串连为一个字符串。
    const spacer = R.join(' ');
    spacer(['a', 2, 3.4]); //=> 'a 2 3.4'
    R.join('|', [1, 2, 3]); //=> '1|2|3'
    <br />
    // 根据指定的分隔符将字符串拆分为字符串类型的数组。
    const pathComponents = R.split('/');
    R.tail(pathComponents('/usr/local/bin/node')); //=> ['usr', 'local', 'bin', 'node']
    R.split('.', 'a.b.c.xyz.d'); //=> ['a', 'b', 'c', 'xyz', 'd']
    <br />
    // 在指定的索引处拆分列表或者字符串。
    R.splitAt(1, [1, 2, 3]); //=> [[1], [2, 3]]
    R.splitAt(5, 'hello world'); //=> ['hello', ' world']
    R.splitAt(-1, 'foobar'); //=> ['fooba', 'r']
    nth,nthArg
    const list = ['foo', 'bar', 'baz', 'quux'];
    R.nth(1, list); //=> 'bar'
    R.nth(-1, list); //=> 'quux'
    R.nth(-99, list); //=> undefined
    <br />
    R.nth(2, 'abc'); //=> 'c'
    R.nth(3, 'abc'); //=> ''
    <br />
    R.nthArg(1)('a', 'b', 'c'); //=> 'b'
    R.nthArg(-1)('a', 'b', 'c'); //=> 'c'
    head,last,init,tail
    // 求列表或字符串的首个元素。在某些库中,该函数也被称作 first。
    R.head(['fi', 'fo', 'fum']); //=> 'fi'
    R.head([]); //=> undefined
    <br />
    R.head('abc'); //=> 'a'
    R.head(''); //=> ''
    <br />
    R.last(['fi', 'fo', 'fum']); //=> 'fum'
    R.last([]); //=> undefined
    <br />
    R.last('abc'); //=> 'c'
    R.last(''); //=> ''
    <br />
    //返回 list 或 string 删除最后一个元素后的部分。
    R.init([1, 2, 3]); //=> [1, 2]
    R.init([1, 2]); //=> [1]
    R.init([1]); //=> []
    R.init([]); //=> []
    <br />
    //删除列表中的首个元素(或者调用对象的 tail 方法)
    R.tail([1, 2, 3]); //=> [2, 3]
    R.tail([1, 2]); //=> [2]
    R.tail([1]); //=> []
    R.tail([]); //=> []
    transpose, xprod,zip, zipObj, zipWith
    // 二维数组行列转置。输入 n 个长度为 x 的数组,输出 x 个长度为 n 的数组。
    R.transpose([[1, 'a'], [2, 'b'], [3, 'c']]) //=> [[1, 2, 3], ['a', 'b', 'c']]
    R.transpose([[1, 2, 3], ['a', 'b', 'c']]) //=> [[1, 'a'], [2, 'b'], [3, 'c']]
    <br />
    // 将两个列表的元素两两组合,生成一个新的元素对列表。
    R.xprod([1, 2], ['a', 'b']); //=> [[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]
    <br />
    // 将两个列表对应位置的元素组合,生成一个新的元素对列表。生成的列表长度取决于较短的输入列表的长度。
    R.zip([1, 2, 3], ['a', 'b', 'c']); //=> [[1, 'a'], [2, 'b'], [3, 'c']]
    <br />
    // 将两个列表对应位置的元素作为键值对组合,生成一个新的键值对的列表。生成的列表长度取决于较短的输入列表的长度。
    R.zipObj(['a', 'b', 'c'], [1, 2, 3]); //=> {a: 1, b: 2, c: 3}
    <br />
    // 将两个列表对应位置的元素通过一个函数处理,生成一个新的元素的列表。生成的列表长度取决于较短的输入列表的长度。
    const f = (x, y) => {
    // ...
    };
    R.zipWith(f, [1, 2, 3], ['a', 'b', 'c']);
    //=> [f(1, 'a'), f(2, 'b'), f(3, 'c')]

    • 高阶函数

    o,compose,composeWith,pipe,pipeWith
    // 类似于 [compose](https://ramda.cn/docs/#compose),o 从右到左执行函数组合。但与 [compose](https://ramda.cn/docs/#compose) 不同的是,传递给 o 的最右边的函数为一元函数。
    const classyGreeting = name => "The name's " + name.last + ", " + name.first + " " + name.last
    const yellGreeting = R.o(R.toUpper, classyGreeting);
    yellGreeting({first: 'James', last: 'Bond'}); //=> "THE NAME'S BOND, JAMES BOND"
    <br />
    R.o(R.multiply(10), R.add(10))(-4) //=> 60
    <br />
    //从右往左执行函数组合(右侧函数的输出作为左侧函数的输入)。最后一个函数可以是任意元函数(参数个数不限),其余函数必须是一元函数。
    const classyGreeting = (firstName, lastName) => "The name's " + lastName + ", " + firstName + " " + lastName
    const yellGreeting = R.compose(R.toUpper, classyGreeting);
    yellGreeting('James', 'Bond'); //=> "THE NAME'S BOND, JAMES BOND"
    <br />
    R.compose(Math.abs, R.add(1), R.multiply(2))(-4) //=> 7
    <br />
    // 利用转换函数从右往左执行函数组合。最后一个函数可以是任意元函数(参数个数不限),其余函数必须是一元函数。
    const composeWhileNotNil = R.composeWith((f, res) => R.isNil(res) ? res : f(res));
    composeWhileNotNil([R.inc, R.prop('age')])({age: 1}) //=> 2
    composeWhileNotNil([R.inc, R.prop('age')])({}) //=> undefined
    <br />
    // 从左往右执行函数组合。第一个函数可以是任意元函数(参数个数不限),其余函数必须是一元函数。
    const f = R.pipe(Math.pow, R.negate, R.inc);
    f(3, 4); // -(3^4) + 1
    <br />
    // 利用转换函数从左往右执行函数组合。第一个函数可以是任意元函数(参数个数不限),其余函数必须是一元函数。
    const pipeWhileNotNil = R.pipeWith((f, res) => R.isNil(res) ? res : f(res));
    const f = pipeWhileNotNil([Math.pow, R.negate, R.inc])
    f(3, 4); // -(3^4) + 1

    • 排序函数

    ascend, descend,sort, sortBy,sortWith
    const byAge = R.ascend(R.prop('age'));
    const people = [
    { name: 'Emma', age: 70 },
    { name: 'Peter', age: 78 },
    { name: 'Mikhail', age: 62 },
    ];
    const peopleByYoungestFirst = R.sort(byAge, people);
    //=> [{ name: 'Mikhail', age: 62 },{ name: 'Emma', age: 70 }, { name: 'Peter', age: 78 }]
    <br />
    const byAge = R.descend(R.prop('age'));
    const people = [
    { name: 'Emma', age: 70 },
    { name: 'Peter', age: 78 },
    { name: 'Mikhail', age: 62 },
    ];
    const peopleByOldestFirst = R.sort(byAge, people);
    //=> [{ name: 'Peter', age: 78 }, { name: 'Emma', age: 70 }, { name: 'Mikhail', age: 62 }]
    <br />
    // 使用比较函数对列表进行排序。比较函数每次接受两个参数,如果第一个值较小,则返回负数;如果第一个值较大,则返回正数;如果两值相等,返回零。注意,返回的是列表的 **拷贝 **,不会修改原列表。
    const diff = function(a, b) { return a - b; };
    R.sort(diff, [4,2,7,5]); //=> [2, 4, 5, 7]
    <br />
    //根据给定的函数对列表进行排序。
    const sortByFirstItem = R.sortBy(R.prop(0));
    const pairs = [[-1, 1], [-2, 2], [-3, 3]];
    sortByFirstItem(pairs); //=> [[-3, 3], [-2, 2], [-1, 1]]
    <br />
    //依据比较函数列表对输入列表进行排序。
    const alice = {
    name: 'alice',
    age: 40
    };
    const bob = {
    name: 'bob',
    age: 30
    };
    const clara = {
    name: 'clara',
    age: 40
    };
    const people = [clara, bob, alice];
    const ageNameSort = R.sortWith([
    R.descend(R.prop('age')),
    R.ascend(R.prop('name'))
    ]);
    ageNameSort(people); //=> [alice, clara, bob]
    comparator
    const byAge = R.comparator((a, b) => a.age < b.age);
    const people = [
    { name: 'Emma', age: 70 },
    { name: 'Peter', age: 78 },
    { name: 'Mikhail', age: 62 },
    ];
    const peopleByIncreasingAge = R.sort(byAge, people);
    //=> [{ name: 'Mikhail', age: 62 },{ name: 'Emma', age: 70 }, { name: 'Peter', age: 78 }]
    <br />
    <br />

    • 辅助函数,处理参数

    apply, call, unapply
    // 将函数 fn 作用于参数列表 args。apply 可以将变参函数转换为为定参函数。如果上下文很重要,则 fn 应该绑定其上下文。
    const nums = [1, 2, 3, -99, 42, 6, 7];
    R.apply(Math.max, nums); //=> 42
    <br />
    R.call(R.add, 1, 2); //=> 3
    //R.call 可以用作 [R.converge](https://ramda.cn/docs/#converge) 的 convergeing 函数:第一个分支函数生成函数,其余分支函数生成一系列值作为该函数的参数。(R.converge 第二个参数为一个分支函数列表
    const indentN = R.pipe(R.repeat(' '),
    R.join(''),
    R.replace(/^(?!$)/gm));
    const format = R.converge(R.call, [
    R.pipe(R.prop('indent'), indentN),
    R.prop('value')
    ]);
    format({indent: 2, value: 'foo\nbar\nbaz\n'}); //=> ' foo\n bar\n baz\n'
    <br />
    // 换言之,R.unapply 将一个使用数组作为参数的函数,变为一个不定参函数。 R.unapply 是 [R.apply](https://ramda.cn/docs/#apply) 的逆函
    R.unapply(JSON.stringify)(1, 2, 3); //=> '[1,2,3]'
    binary,nAry,unary
    const takesTwoArgs = R.binary(takesThreeArgs);
    takesTwoArgs.length; //=> 2
    takesTwoArgs(1, 2, 3); //=> [1, 2, undefined]
    <br />
    // 将一个任意元(包括零元)的函数,封装成一个确定元数(参数个数)的函数。任何多余的参数都不会传入被封装的函数。
    const takesOneArg = R.nAry(1, takesTwoArgs);
    takesOneArg.length; //=> 1
    takesOneArg(1, 2); //=> [1, undefined]
    <br />
    // 将任意元(包括零元)函数封装成一元函数。任何额外的参数都不会传递给被封装的函数。
    const takesOneArg = R.unary(takesTwoArgs);
    takesOneArg.length; //=> 1
    takesOneArg(1, 2); //=> [1, undefined]
    partial, partialRight,curry, curryN, uncurryN
    const multiply2 = (a, b) => a * b;
    const double = R.partial(multiply2, [2]);
    double(2); //=> 4
    <br />
    const greet = (salutation, title, firstName, lastName) =>
    salutation + ', ' + title + ' ' + firstName + ' ' + lastName + '!';
    const sayHello = R.partial(greet, ['Hello']);
    const sayHelloToMs = R.partial(sayHello, ['Ms.']);
    sayHelloToMs('Jane', 'Jones'); //=> 'Hello, Ms. Jane Jones!'
    <br />
    const greet = (salutation, title, firstName, lastName) =>
    salutation + ', ' + title + ' ' + firstName + ' ' + lastName + '!';
    const greetMsJaneJones = R.partialRight(greet, ['Ms.', 'Jane', 'Jones']);
    greetMsJaneJones('Hello'); //=> 'Hello, Ms. Jane Jones!'
    <br />
    const addFourNumbers = (a, b, c, d) => a + b + c + d;
    const curriedAddFourNumbers = R.curry(addFourNumbers);
    const f = curriedAddFourNumbers(1, 2);
    const g = f(3);
    g(4); //=> 10
    <br />
    const sumArgs = (...args) => R.sum(args);
    const curriedAddFourNumbers = R.curryN(4, sumArgs);
    const f = curriedAddFourNumbers(1, 2);
    const g = f(3);
    g(4); //=> 10
    <br />
    // 将一个柯里化的函数转换为一个 n 元函数。
    const addFour = a => b => c => d => a + b + c + d;
    const uncurriedAddFour = R.uncurryN(4, addFour);
    uncurriedAddFour(1, 2, 3, 4); //=> 10

    • 其他

    clamp
    R.clamp(1, 10, -5) // => 1
    R.clamp(1, 10, 15) // => 10
    R.clamp(1, 10, 4) // => 4
    clone
    // 深复制。其值可能(嵌套)包含 Array、Object、Number、String、Boolean、Date 类型的数据。Function 通过引用复制。
    const objects = [{}, {}, {}];
    const objectsClone = R.clone(objects);
    objects === objectsClone; //=> false
    objects[0] === objectsClone[0]; //=> false
    empty
    // 根据传入参数的类型返回其对应的空值。Ramda 定义了各类型的空值如下:Array ([]),Object ({}),String (''),和 Arguments
    R.empty(Just(42)); //=> Nothing()
    R.empty([1, 2, 3]); //=> []
    R.empty('unicorns'); //=> ''
    R.empty({x: 1, y: 2}); //=> {}
    flip
    // 交换函数前两个参数的位置。
    const mergeThree = (a, b, c) => [].concat(a, b, c);
    mergeThree(1, 2, 3); //=> [1, 2, 3]
    R.flip(mergeThree)(1, 2, 3); //=> [2, 1, 3]
    tap
    // 对输入的值执行给定的函数,然后返回输入的值。
    const sayX = x => console.log('x is ' + x);
    R.tap(sayX, 100); //=> 100
    // logs 'x is 100'
    type
    // 用一个单词来描述输入值的(原生)类型,返回诸如 'Object'、'Number'、'Array'、'Null' 之类的结果。不区分用户自定义的类型,统一返回 'Object'。
    R.type({}); //=> "Object"
    R.type(1); //=> "Number"
    R.type(false); //=> "Boolean"
    R.type('s'); //=> "String"
    R.type(null); //=> "Null"
    R.type([]); //=> "Array"
    R.type(/[A-z]/); //=> "RegExp"
    R.type(() => {}); //=> "Function"
    R.type(undefined); //=> "Undefined"
    keys,keysIn,values, valuesIn
    // 返回给定对象所有可枚举的、自身属性的属性名组成的列表。注意,不同 JS 运行环境输出数组的顺序可能不一致。
    R.keys({a: 1, b: 2, c: 3}); //=> ['a', 'b', 'c']
    <br />
    // 返回给定对象所有属性(包括 prototype 属性)的属性名组成的列表。注意,不同 JS 运行环境输出数组的顺序可能不一致。
    const F = function() { this.x = 'X'; };
    F.prototype.y = 'Y';
    const f = new F();
    R.keysIn(f); //=> ['x', 'y']
    <br />
    // 返回对象所有自身可枚举的属性的值。注意:不同 JS 运行环境输出数组的顺序可能不一致。
    R.values({a: 1, b: 2, c: 3}); //=> [1, 2, 3]
    <br />
    // 返回对象所有属性的值,包括原型链上的属性。注意:不同 JS 运行环境输出数组的顺序可能不一致。
    const F = function() { this.x = 'X'; };
    F.prototype.y = 'Y';
    const f = new F();
    R.valuesIn(f); //=> ['X', 'Y']
    memoizeWith,once
    // 创建一个新函数,当调用时,会执行原函数,输出结果;并且缓存本次的输入参数及其对应的结果。 后续,若用相同的参数对缓存函数进行调用,不会再执行原函数,而是直接返回该参数对应的缓存值。
    let count = 0;
    const factorial = R.memoizeWith(R.identity, n => {
    count += 1;
    return R.product(R.range(1, n + 1));
    });
    factorial(5); //=> 120
    factorial(5); //=> 120
    factorial(5); //=> 120
    count; //=> 1
    <br />
    // 创建一个只执行一次的函数。将给定函数 fn 封装到新函数fn'中,fn' 确保 fn 只能调用一次。重复调用fn' ,只会返回第一次执行时的结果。
    const addOneOnce = R.once(x => x + 1);
    addOneOnce(10); //=> 11
    addOneOnce(addOneOnce(50)); //=> 11
    test,match
    // 检测字符串是否匹配给定的正则表达式。
    R.test(/^x/, 'xyz'); //=> true
    R.test(/^y/, 'xyz'); //=> false
    <br />
    // 正则匹配字符串。注意,如果没有匹配项,则返回空数组。和 [String.prototype.match](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match) 不同,后者在没有匹配项时会返回 null。
    R.match(/([a-z]a)/g, 'bananas'); //=> ['ba', 'na', 'na']
    R.match(/a/, 'b'); //=> []
    R.match(/a/, null); //=> TypeError: null does not have a method named "match"
    range
    // 返回从 from 到 to 之间的所有数的升序列表。左闭右开(包含 from,不包含 to)。
    R.range(1, 5); //=> [1, 2, 3, 4]
    R.range(50, 53); //=> [50, 51, 52]
    repeat,times
    // 生成包含 n 个同一元素的数组。
    R.repeat('hi', 5); //=> ['hi', 'hi', 'hi', 'hi', 'hi']
    const obj = {};
    const repeatedObjs = R.repeat(obj, 5); //=> [{}, {}, {}, {}, {}]
    repeatedObjs[0] === repeatedObjs[1]; //=> true
    <br />
    //执行输入的函数 n 次,返回由函数执行结果组成的数组。fn 为一元函数,n 次调用接收的参数为:从 0 递增到 n-1 。
    R.times(R.identity, 5); //=> [0, 1, 2, 3, 4]
    toLower, toUpper
    // 将字符串转换成小写。
    R.toLower('XYZ'); //=> 'xyz'
    <br />
    // 将字符串转换为大写。
    R.toUpper('abc'); //=> 'ABC'
    tryCatch
    // tryCatch 接受两个函数:tryer 和 catcher,生成的函数执行 tryer,若未抛出异常,则返回执行结果。若抛出异常,则执行 catcher,返回 catcher 的执行结果
    R.tryCatch(R.prop('x'), R.F)({x: true}); //=> true
    R.tryCatch(() => { throw 'foo'}, R.always('catched'))('bar') // => 'catched'
    R.tryCatch(R.times(R.identity), R.always([]))('s') // => []
    R.tryCatch(() => { throw 'this is not a valid value'}, (err, value)=>({error : err, value }))('bar') // => {'error': 'this is not a valid value', 'value': 'bar'}
    until
    // 接受一个 predicate ,transform function 和 初始值,返回一个与初始值相同类型的值。对输入值进行 transform ,直到 transform 的结果满足 predicate,此时返回这个满足 predicate 的值。
    R.until(R.gt(R.__, 100), R.multiply(2))(1) // => 128
    相关文档:
    Ramda.js与函数式编程
    ramda使用整理.md

    暂时未理解到的用法:
    andThen ,otherwise,Bind,Both,either,Composek,pipeK,Composep,pipeP,construct,invoker,constructN,Thunkify,sequence,traverse,useWith?