用大白话介绍柯里化函数

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

官方解释

先是查看了柯里化的介绍java

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

这句话听起来有点拗口啊,反正我没能彻底理解,不过不要紧,咱们能够查看网上大牛们的解释,通过一顿搜索,算是屡清楚了头绪。segmentfault

个人理解

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

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

而假如,咱们想让调用更加灵活一些,好比改为下面这种调用方式app

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
复制代码

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

柯里化函数的特色

经过上面的栗子咱们能够看出柯里化函数有这么几个特色:

  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…