函数柯里化

166 阅读4分钟

什么是函数柯里化

柯里化是一种使用将多个参数的函数转换成一系列使用一个参数的函数的技术;
eg:一个接收三个参数的普通函数,进行柯里化后,柯里化版本的函数接收一个参数并返回接收下一个参数的函数,该函数返回一个接收第三个参数的函数,最后一个函数在接收到第三个参数后,将之前接收到的三个参数应用于原普通函数中,并返回最终结果。

// 一个接收三个参数的普通函数
function sum(a, b, c) {
    return a + b + c;
}

// 用于将普通函数转为柯里化函数的工具
function curry(fn) {
   // ...一系列逻辑代码,返回一个新函数
}

// 获取第一个柯里化后的函数
let _sum = curry(sum);

// 返回一个接收第二个参数的函数
const A = _sum(1);
// 返回一个接收第三个参数的函数
const B = A(2);
// 接收到最后一个参数,将之前所有接收到的参数应用于原函数中,并运行
B(3); // log 6;

在数学和计算机柯里化中,一次只能传递一个参数
在js中,一次可传多个参数,如下:

// 普通函数
function fn(a, b, c, d, e) {
    console.log(a, b, c, d, e);
}

// 生成柯里化函数
const _fn = curry(fn);

_fn(1, 2, 3, 4, 5) // log 1, 2, 3, 4, 5
_fn(1)(2)(3, 4, 5) // log 1, 2, 3, 4, 5
_fn(1, 2)(3, 4)(5) // log 1, 2, 3, 4, 5
_fn(1)(2)(3)(4)(5) // log 1, 2, 3, 4, 5

1. 对于已经柯里化后的函数_fn来说,如果接收到的函数参数数量与原函数的形参数量相同时,执行原函数
2. 当接收到的参数数量小于原函数时,返回一个接收剩余参数的函数,直到接收到的函数参数数量等于原函数行参数量,执行原函数

柯里化的作用

柯里化实质是把简单问题复杂化了,但在复杂化的过程中,提高的函数的自由度
本质是降低了函数的通用性,提高了函数的适用性; 日常使用中可能用到柯里化的场景:

// 我们工作中会遇到各种需要通过正则检验的需求
// 比如校验电话号码、校验邮箱、校验身份证号、校验密码等,
// 这时我们会封装一个通用函数 checkByRegExp ,接收两个参数,校验的正则对象和待校验的字符串

function checkByRegExp(regExp,string) {
    return regExp.test(string);  
}

checkByRegExp(/^1\d{10}$/, '18642838455'); // 校验电话号码
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com'); // 校验邮箱

// 但是我们考虑这样一个问题,如果我们需要校验多个电话号码或者校验多个邮箱呢?

checkByRegExp(/^1\d{10}$/, '18642838455'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '13109840560'); // 校验电话号码
checkByRegExp(/^1\d{10}$/, '13204061212'); // 校验电话号码

checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com'); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@qq.com'); // 校验邮箱
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@gmail.com'); // 校验邮箱

我们每次进行校验的时候都需要输入一串正则,再校验同一类型的数据时,
相同的正则我们需要写多次, 这就导致我们在使用的时候效率低下,
并且由于 checkByRegExp 函数本身是一个工具函数并没有任何意义, 
一段时间后我们重新来看这些代码时,如果没有注释,我们必须通过检查正则的内容, 
我们才能知道我们校验的是电话号码还是邮箱,还是别的什么。

此时,我们可以借助柯里化对 checkByRegExp 函数进行封装,以简化代码书写,提高代码可读性。

//进行柯里化
let _check = curry(checkByRegExp);
//生成工具函数,验证电话号码
let checkCellPhone = _check(/^1\d{10}$/);
//生成工具函数,验证邮箱
let checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);

checkCellPhone('18642838455'); // 校验电话号码
checkCellPhone('13109840560'); // 校验电话号码
checkCellPhone('13204061212'); // 校验电话号码

checkEmail('test@163.com'); // 校验邮箱
checkEmail('test@qq.com'); // 校验邮箱
checkEmail('test@gmail.com'); // 校验邮箱

如何实现柯里化

柯里化的定义,接收一部分参数,返回一个函数接收剩余参数,接收足够参数后,执行原函数。
如何判断接收到的参数达到足够的参数了呢?
有两种判断方法:

  1. 通过函数的length属性,获取函数的行参个数,行参的个数就是所需的参数个数。
  2. 在调用柯里化工具函数时,手动指定所需的函数个数
    结合这两点,实现一个简单的curry函数
function curry(fn, len = fn.length) {
    return _curry.call(this, fn, len);
}

function _curry(fn, len, ...args) {
    return function(...parms) {
        let _args = [...args, ...parms];
        if(_args.length >= len) {
            return fn.apply(this, _args);
        } else {
            return _curry.call(this, fn, len, ..._args)
        }
    }
}

参考文献:
彻底弄懂函数柯里化