用大白话介绍柯里化函数

最近在学习函数式编程,看到柯里化函数这个东东,原以为是个新的概念,没想到一查,竟然老早就存在了,且已经成熟运用多年,深感惭愧啊,我到现在都还没接触过。

官方解释

先是查看了柯里化的介绍

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

这句话听起来有点拗口啊,反正我没能完全理解,不过没关系,我们可以查看网上大牛们的解释,经过一顿搜索,算是屡清楚了头绪。

我的理解

我们先来看个栗子,这是一个求和的函数,求4个参数的和,那么我们在使用这个函数的时候,就要先知道每个参数的具体的值,然后才能调用这个函数。

function add(a, b, c, d) {
    return a + b + c + d;
};
console.log(add(2,3,4,5))  // 14
复制代码

而假如,我们想让调用更加灵活一些,比如改成下面这种调用方式

console.log(add(2)(3)(4)(5))  // 14
复制代码

可以看到,这种调用方式有个好处,不需要等待所有参数明确后在执行具体方法,当在参数不满足的情况下,每次调用返回的是函数,比如 add(2) 返回的是一个函数,返回的函数不断的接受新的参数,直到满足预定好的4个参数时,便输出结果。 而 add(2,3,4,5) => add(2)(3)(4)(5) 这一个过程便是函数柯里化的过程。这样子解释,不知道大伙好理解不?

talk is cheap , show me the code,ok 那我们看下具体的实现,先写一个函数将 add() 转换成柯里化函数

/**
 * 转换函数
 * @param {*} fn 目标函数
 * @param  {...any} args 其他参数
 */
const createCurry = (fn, ...args) => {
    // 获取目标函数的参数个数
    let length = fn.length;
    return (...rest) => {
        // 将已有的参数和新的参数合并
        let allArgs = args.slice(0);
        allArgs.push(...rest);
        // 若参数个数已经满足目标函数的参数要求,则执行目标函数。否则继续返回转换函数
        if (allArgs.length < length) {
            return createCurry.call(this, fn, ...allArgs)
        } else {
            return fn.apply(this, allArgs)
        }

    }
}
function add(a, b, c, d) {
    return a + b + c + d;
};
复制代码

使用如下:

const curryAdd = createCurry(add,2);
const sum = curryAdd(3)(4)(5);    

console.log(sum) // 14
复制代码

转换柯里化函数有个关键的点,那就是要明确触发条件,比如说上面的栗子中,我们的触发条件是参数的个数,根据参数的个数来区分返回的是函数还是具体的结果。

柯里化函数的特点

通过上面的栗子我们可以看出柯里化函数有这么几个特点:

  1. 参数复用
  2. 业务解耦,调用时机灵活
  3. 延迟执行,部分求值

继续举栗子

光说理论太枯燥,那么我们把上面的栗子在稍微扩展下,把add函数改成不定参数,就是说,我可以传n个参数,求这n个参数的和,调用如下

const curryAdd = createCurry(add);
const sum = curryAdd(3)(4)(5); // 12  
const sum = curryAdd(3)(4)(5)(6); // 18  
复制代码

OK,首先我们要对add函数做下修改

/**
 * 不定参数求和
 * @param  {...any} arg 
 */
function add(...arg){
    return arg.reduce((result,value)=>{ return result += value },0)
}
复制代码

那么这个函数使用就变成 add(1,2,3),但我们的目标是这样的 add(1)(2)(3) ,在写转换函数之前,我们先要确定我们的触发条件,我想到的条件是:当新传入的参数个数为 0 的时候,就是没有参数,就执行目标函数,否则返回函数。那么柯里化函数的调用方式需要稍微改成如下

add(1)(2)(3)();
复制代码

具体的转换函数和调用如下

const createCurry = (fn, ...args) => {
    return (...rest) => {
        let allArgs = args.slice(0);
        allArgs.push(...rest)
        // 触发条件
        if (rest.length !== 0) {
            return createCurry.call(this, fn, ...allArgs)
        } else {
            return fn.apply(this, allArgs)
        }

    }
}
const curryAdd = createCurry1(add);
console.log(curryAdd(1)(2)(3)(4)()) // 10
复制代码

柯里化到底是什么?

通过上面两个小栗子,大伙应该有个大致概念了,所以柯里化是一种设计思想,主动掌握了函数的控制权,根据我们的需要,设定相应的触发机制。有句话,让代码编写代码,柯里化算是有那么点意思吧,函数生成函数,函数去调用函数,我们只要编写原子性的函数和设定好条件。

再来两个栗子

数组处理

写个通用的数组处理柯里化函数,currying的第一个参数是目标函数,currying直接返回函数,没有特定的触发条件,在使用mapSQ([1, 2, 3, 4, 5]) 会将数组和currying的第二个参数一起传给目标函数,即currying第一个参数,详细如下:

function currying(fn) {
    var slice = Array.prototype.slice,
        __args = slice.call(arguments, 1);
    return function () {
        var __inargs = slice.call(arguments);
        return fn.apply(null, __args.concat(__inargs));
    };
}

function square(i) {
    return i * i;
}
function double(i) {
    return i *= 2;
}

function map(handeler, list) {
    return list.map((value) => {
        return handeler(value)
    });
}

var mapSQ = currying(map, square); // 平方
mapSQ([1, 2, 3, 4, 5]); //[1, 4, 9, 16, 25]

var mapTwo = currying(map, double); // 两倍
mapSQ([1, 2, 3, 4, 5]); //[2,4,6,8,10]

复制代码
以参数长度为条件的转换函数

这个栗子是在 30secondsofcode 上看到,我觉得写得非常精简直观,给大家分享下

const curry = (fn, arity = fn.length, ...args) => arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);

curry(Math.pow)(2)(10); // 1024
curry(Math.min, 3)(10)(50)(2); // 2
复制代码

官方写成了一行,阅读可能不太方便,我改成通俗版,加了些注释

/**
 * 生成柯里化函数,以参数长度达标作为触发条件
 * @param {*} fn 目标函数
 * @param {*} arity 目标函数参数个数
 * @param  {...any} args 调用传入的参数
 */
const curry = (fn, arity = fn.length, ...args) => {
    if (arity <= args.length) {
        return fn(...args)
    } else {
        return curry.bind(null, fn, arity, ...args);
    }
}
复制代码

小结

函数柯里化可以给我们带来很多想象,可以将耦合的业务逻辑拆解,使得函数编程更加纯粹。不过我个人觉得柯里化函数要是太复杂,对大大降低代码的可阅读性和可维护性,所以柯里化虽然看着高大上,但还是不能滥用。

参考资料

baike.baidu.com/item/柯里化

segmentfault.com/a/119000001…

转载于:https://juejin.im/post/5cb30832e51d456e8240dcbd

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值