柯里化

486 阅读4分钟

前言

最近在学柯里化,一直都不太理解,直到综合看了几篇文章,才感觉懂了一些,把这个学习过程记录下来,希望能帮到像我这样初学者,参考的文章放在结尾,大家可以去看一下。

1、柯里化

1.1 什么叫柯里化?

维基百科中,柯里化是指在数学和计算机科学中,柯里化是一种将使用很多个参数的一个函数转换成一系列使用一个参数的函数的技术。也可以理解为提前接受部分参数,延迟执行,不立即输出结果,而是返回接受剩余参数的函数。因为这样的特性,也被称为部分计算函数。柯里化,是 一个逐步接收参数的过程。

1.2 简单实现一个柯里化
    function add(a,b,c){
        return a+b+c
    }

当我们执行一个add函数,需要add(1,2,3)执行,柯里化就是add(1)(2)(3),add(1,2)(3)这样也可以执行,那我们该怎么实现这个功能呢? 首先实现第一个 add(1)(2)(3),我们可以采用下面的方式,借用闭包的原理,当执行完第一个函数的时候,a因为闭包的原因被保存到了内存中。

    function add(a){
        return function(b){
            return function(c){
                return a+b+c
            }
        }
    }

实现第二个add(1,2)(3),同理也是借用闭包的原理。

    function add(a,b){
        return function(c){
            return a+b+c
        }
    }

虽然我们实现了多个参数的分开输入,但是这种方法不具有普遍性,那我们接下来就改一个具有普遍性的,柯里化函数的运行过程其实就是一个参数的收集过程,我们将每一次传递过来的参数收集起来,并在最后一个参数传入时进行执行。

我们先实现一个简易的柯里化。

function currying(fn){
    var allArgs=[];
    return function next(){
        var args=[].slice.call(arguments)
        if(args.length>0){
            allArgs=allArgs.concat(args);
            return next;
        }
        else{
            return fn.apply(null,allArgs);
        }
    }
}
var add=currying(function(){
    var sum=0;
    for(var i=0; i<arguments.length;i++){
        sum+=arguments[i]
    }
    return sum;
})
console.log(add(1,3)(2,3)());

分析一下,这种方式,首先执行var add=currying(fn)fn allArgs缓存到内存中,add变量已经指向了next方法,此时的fn arrArgs已经形成了闭包,在后面会一直存在。我们输入参数后,记载在allArgs中,返回的仍然是next方法,这种方法解决了可以传输多个任意值,结束参数输入的时候直接使用()进行调用。我也是从这里才明白该怎么写柯里化的。但是还存在一定的问题,我们必须使用()进行调用,因此还可以进行改写。

function currying(fn,allArgs){
    var len=fn.length;
    allArgs=allArgs||[];
    return function(){
        var args=[].slice.call(arguments);
        allArgs=allArgs.concat(args);
        if(allArgs.length<len){
            return currying.call(this,fn,allArgs)
        }
        else{
           return fn.apply(this,allArgs);
        }
    }
}
var add=currying(function(a,b,c){
    return a+b+c;
})
console.log(add(1)(2)(3))

这种方式将fn的参数和curring参数利用闭包记载到内存中,add返回的是一个可以接收参数的函数,我们可以通过add()对其进行调用,首先判断add()参数是否小于fn参数,如果小于就返回currying()继续对参数进行收集,如果大于或等于,就调用fn执行。柯里化的核心就是借助闭包实现参数收集,执行的函数相当于执行自身,柯里化就是将问题变得复杂了,但是这样子有更多的自由度。

下面再来一个es6版本的

const currying = fn =>judge = (...args) =>args.length >= fn.length? fn(...args): (...arg) => judge(...args, ...arg)
 //参考自 segmentfault 的@大笑平
1.3柯里化的用途

既然柯里化将问题搞复杂了,我们为什么还要学习,因为柯里化能够应付更加复杂的逻辑封装,我们看下一个例子

如果我们想要验证一串数字是否是正确的手机号,可以这样封装

function checkPhone(phoneNumber){
    return /1[34578]\d{9}/.test(phoneNumber);
}

那如果我们想要验证邮箱呢

function checkEmail(email){
    return /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/.test(email)
}

还有密码,用户名,身份证号.....

因此在实践中,我们通常会封装一个更加通用的函数,用于验证字符串;

function check(targetString,reg){
    return reg.test(targetString);
}
check(/^1[34578]\d{9}$/, '14900000088');
check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com');

但是这种封装使用时,还要输入正则表达式,如果数据很多就效率很低,因此我们换个思路

function checkCurrying(reg) {
    return function(targetString){
        return reg.test(targetString);
    }
}
var checkPhone = checkCurrying(/^1[34578]\d{9}$/);
var checkEmail = checkCurrying(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
checkPhone('1388888876');

也可以利用我们上面写的currying函数

function checkCurrying(fn,allArgs){
    var len=fn.length;
    allArgs=allArgs||[];
    return function(){
        var args=[].slice.call(arguments);
        allArgs=allArgs.concat(args);
        if(allArgs.length<len){
            return currying.call(this,fn,allArgs)
        }
        else{
            return fn.apply(this,allArgs);
        }
    }
}
function check(targetString,reg){
    return reg.test(targetString);
}
var _check=checkCurrying(check)
var checkPhone = _check(/^1[34578]\d{9}$/);
var checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
checkPhone('183888888');
checkEmail('xxxxx@test.com');

虽然柯里化在一定程度上将问题复杂化了,但是柯里化在复杂情况下更加灵活;

在实际项目上,针对某一个数组的操作是固定的,同样的操作可能在项目的不同地方被调用很多次。这时候我们可以对map函数进行二次封装,下面我们对一个数组的每一项转化为1-->100%。

function getArray(array){
    return array.map(function(item){
        return item*100+'%'
    })
}
getArray([1,2,3,4,5]);

借助柯里化来实现二次封装

function currying(fn,allArgs){
    var len=fn.length;
    allArgs=allArgs||[];
    return function(){
        var args=[].slice.call(arguments);
        allArgs=allArgs.concat(args);
        if(allArgs.length<len){
            return currying.call(this,fn,allArgs)
        }
        else{
           return fn.apply(this,allArgs);
        }
    }
}
var _getNewArray=currying(function(fun,array){
    return array.map(fun);
});
var getNewArray=_getNewArray(function(item){
    return item*100+%;
})

大家觉得有帮助的话,麻烦点一下赞,谢谢大家。

深入详解函数的柯里化

柯里化和反柯里化