最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 函数式编程--柯理化(Currying)

    正文概述 掘金(老刀)   2021-03-11   587

    基本概念

    上面是百度百科以及维基百科关于柯理化的定义,单纯从字面上面理解是很困难的。 简单说,柯里化(Currying)是一种处理多元函数的方法。它产生一系列连锁函数,其中每个函数固定部分参数,并返回一个新函数,用于传回其它剩余参数的功能

    下面我们通过一些实例,拆解和说明一下柯理化的具体含义。

    这是一个普通的三元函数,执行运算得出三个参数的和。

    function add(a,b,c){
        return a + b + c
    }
    
    add(1,2,3) // 6
    

    按照上面的定义,add函数的的柯理化转换过程:add(a,b,c) => curriedAdd(a)(b)(c)。

    实现代码如下:

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

    执行验证

    add(1,2,3) // 6
    curriedAdd(1)(2)(3) // 6
    

    上面是基于极简场景的实现,完整的curriedAdd函数,应当是满足下面的调用场景:

    1. 初始传入一个参数,curriedAdd(1)(2,3) 或者 curriedAdd(1)(2)(3)

      • 如果首次执行curriedAdd只传入一个参数,那么将返回一个接受剩余两个参数的函数currying1
      • 如果继续执行currying1,并传入两个参数,将返回执行结果a+b+c;但是,如果继续执行currying1,并传入一个参数,那么将返回一个接受第三个参数的函数currying2
      • 如果继续执行curryring2,并传入参数,将返回执行结果a+b+c
    2. 初始传入两个参数,curriedAdd(1,2)(3)

      • 如果首次执行curriedAdd,并传入两个参数,那么将返回一个接受最后一个参数的函数curryring1
      • 如果继续执行curryring1,并传入参数,将返回执行结果a+b+c
    3. 初始传入三个参数,curriedAdd(1,2,3)

      • 如果首次执行curriedAdd,直接传入三个参数,那么将直接返回执行结果a+b+c

    把上述拆解分析的逻辑过程,转化成函数:

    function curriedAdd() {
        let args = [].slice.call(arguments)
        if (args.length >= 3) {
            return args[0] + args[1] + args[2]
        } else if (args.length === 2) {
            return function () {
                return args[0] + args[1] + arguments[0]
            }
        } else if (args.length === 1) {
            return function c() {
                let args1 = [].slice.call(arguments)
                if (args1.length >= 2) {
                    return args[0] + args1[0] + args1[0]
                } else if(args1.length === 1) {
                    return function () {
                        return args[0] + args1[0] + arguments[0]
                    }
                }else{
                    return c
                }
            }
        } else {
            return curriedAdd
        }
    }
    

    执行验证

    
    console.log(curriedAdd(1)) // [Function (anonymous)]
    console.log(curriedAdd(1)(2)) // [Function (anonymous)]
    console.log(curriedAdd(1)(2)(3)) // 6
    
    console.log(curriedAdd(1, 2)) // [Function (anonymous)]
    console.log(curriedAdd(1, 2)(3)) // 6
    
    console.log(curriedAdd(1, 2, 3)) // 6
    
    

    验证实例运行结果:

    [Function (anonymous)]
    [Function (anonymous)]
    6
    [Function (anonymous)]
    6
    6
    

    总之,柯理化是把一个多元函数,转换成一系列更少元函数的处理方法。

    实现原理

    实现一个二元或者三元的柯理化函数相对简单,就像上面的curriedAdd函数。难的是实现一个任意元函数的通用柯理化函数。 基于上面的分析,实现一个通用的柯理化函数,需要以下几个条件: 1、首先需要一个函数fn作为参数; 2、其次,可以获取到fn声明时虚参的数量,通过fn.length属性可以实现; 3、最后,可以判断返回接受剩余参数的新函数,或者返回fn(...参数)执行结果,以及缓存已经固定的参数。通过fn.length、闭包和递归可以实现。

    function currying(fn) {
        return function curried() {
            var args = [].slice.call(arguments),
                context = this
    
            return args.length >= fn.length ?
                fn.apply(context, args) :
                function () {
                    var rest = [].slice.call(arguments)
                    return curried.apply(context, args.concat(rest))
                }
        }
    }
    

    通过currying创建上面的curriedAdd函数,执行验证得出相同的结果。

    var curriedAdd = currying(add)
    
    console.log(curriedAdd(1)) // [Function (anonymous)]
    console.log(curriedAdd(1)(2)) // [Function (anonymous)]
    console.log(curriedAdd(1)(2)(3)) // 6
    
    console.log(curriedAdd(1, 2)) // [Function (anonymous)]
    console.log(curriedAdd(1, 2)(3)) // 6
    
    console.log(curriedAdd(1, 2, 3)) // 6
    
    

    此外,还有一个问题需要补充说明。对参数不固定的函数进行柯理化变换是没有意义的。 例如,下面这个对不定数量的数字进行排序的sort函数。函数声明时参数的数量并不确定。通过sort.length获取到的虚参的数量是0,无论给curriedSort传入多少参数都会立即执行。

    function sort(){
        return [].slice.call(arguments).sort(function(a,b){
            return a-b
        })
    }
    
    sort(1,3,6,2) // [1,2,3,6]
    
    var curriedSort = currying(sort)
    var currying1 = curriedSort(1,3,6,2) // [1,2,3,6]
    
    currying1(5) // TypeError: curriedSort(...) is not a function
    

    虽然无法通过length属性获取到不确定参数的长度,但是可以再柯理化转换的同时,指定目标参数长度,用于替代sort.length的作用。下面调整一下currying,适配参数不固定的函数。

    function currying(fn, len) {
        return function curried() {
            var args = [].slice.call(arguments),
                context = this
            var _len = fn.length || len
    
            return args.length >= _len ?
                fn.apply(context, args) :
                function () {
                    var rest = [].slice.call(arguments)
                    return curried.apply(context, args.concat(rest))
                }
        }
    }
    
    var curriedSort = currying(sort,5)
    var currying1 = curriedSort(1,3,6,2) // [Function (anonymous)]
    
    currying1(5) // [1,2,3,5,6]
    
    

    应用实践

    • 解决重复传参问题,提高函数适用性

    柯理化(currying)应用很广泛也很常见。比如,批量发送双11活动邮件,通常我们这样做

    function sendEmail(from, content, to){
        console.log(`${from} send email to ${to}, content is ${content}`)
    }
    
    sendEmail('xx公司', '双11优惠折上5折', 'zhangsan@xx.com')
    sendEmail('xx公司', '双11优惠折上5折', 'lisi@xx.com')
    sendEmail('xx公司', '双11优惠折上6折', 'wangwu@xx.com')
    sendEmail('xx公司', '双11优惠折上6折', 'maliu@xx.com')
    
    // ...
    

    邮件发送方是固定的,邮件内容是相对固定的,唯一不同的是邮件的接受者。这正符合柯理化(currying)固定部分参数,并返回接受剩余参数新函数的规则。柯理化创建两个临时性的、适用性更强的函数sendEmailToS5和sendEmailToS6,向目标群体,发送指定类型的邮件。

    var sendEmailContent = currying(sendEmail)('xx公司')
    var sendEmailToS5 = sendEmailContent('双11优惠折上5折')
    var sendEmailToS5 = sendEmailContent('双11优惠折上6折')
    
    // 打五折的群组
    sendEmailToS5('zhangsan@xx.com')
    sendEmailToS5('lisi@xx.com')
    
    // ...
    
    // 打六折的群组
    sendEmailToS6('wangwu@xx.com')
    sendEmailToS6('maliu@xx.com')
    
    // ...
    
    

    因此,柯理化(currying)可以解决重复传参的问题,并提高函数功能的适用性。

    • 降低函数参数元次,适配应用

    通常,在创建工具函数时,我们尽量使其更加抽象,以提高其通用性。但是,这样做的弊端也很明显,会降低其适用性。比如,我们创建一个获取对象目标属性的函数getObjKeys(obj,keys)

    function getObjKeys(keys, obj){
        var o = {}
        keys.forEach(function(k){
            o[k] = obj[k]
        })
        return o
    }
    
    var person = {
        name:'zhangsan',
        age: 20,
        work: 'worker',
        tel: '13699887766'
    }
    
    getObjKeys(['name','tel'], person) // {name:'zhangsan',tel:'13699887766'}
    

    假设,另外一个场景,我们需要查询车间,所以worker的姓名、年龄和电话。我们可以这样做

    workers.map(function(worker){
        return getObjKeys(worker,['name','age','tel'])
    })
    

    除此之外,利用柯理化,我们可以固定getObjKeys函数keys参数,同时得到一个接受另外一个参数obj的函数。这个函数,可以作为map函数的callback直接被使用。

    var callback = currying(getObjKeys)(['name','age','tel'])
    workers.map(callback) // [{name,age,tel},...]
    
    

    参考资料

    baike.baidu.com/item/%E6%9F… github.com/shfshanyue/… juejin.cn/post/684490… developer.mozilla.org/zh-CN/docs/… www.yuque.com/webqiang/qn… github.com/mqyqingfeng…


    起源地下载网 » 函数式编程--柯理化(Currying)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元