JavaScript 函数柯里化和偏函数

1.柯里化

柯里化(英语:Currying),又译为卡瑞化加里化。在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术

如果我们需要实现一个求三个数之和的函数:

function add(a, b, c) {
  return a + b + c;
}
console.log(add(2, 3, 5)); // 10

如果按照柯里化思想,我们可以想到:

function add(a) {  
   return function(b) {
     return function(c) {
       return a+b+c;
     }
   }
}
console.log(add(2)(3)(5))  //10

但是,这种写法缺乏拓展性


1.1 实现对有特定参数个数的函数柯里化

addCurry(2)(3)(5),addCurry(2,3)(5)和addCurry(2,3,5)输出10

function curry(func){
    return function curriedFn(...arg){
        if(arg.length < func.length){  //如果实际参数的个数小于形参个数,我们要返回一个新的函数
            return function () {
                return curriedFn(...arg.concat(Array.from(arguments)))
            }
        }
        //实际参数大于等于形参个数
        return func(...arg)
    }
}

var addCurry=curry(function (a,b,c){
    return a+b+c;
})
console.log(addCurry(2)(3)(5))  //10
console.log(addCurry(2,3)(5))   //10
console.log(addCurry(2,3,5))    //10

实现逻辑就是判断参数是否已经达到预期的值(函数柯里化之前的参数个数),如果没有继续返回函数,达到了就执行函数然后返回值。但这一版柯里化函数仍然不能完全满足要求,因为它只针对有特定参数个数的函数适用

我们来看一个
例子:比如开发者编写代码的时候会在应用的不同阶段编写很多日志。我们可以编写一个如下的日志函数:

const loggerHelper = (mode,initialMessage,errorMessage,lineNo) => {
    if(mode === "DEBUG"){
        console.debug(initialMessage,errorMessage + "at line:" + lineNo)
    }else if(mode === "ERROR"){
        console.error(initialMessage,errorMessage + "at line:" + lineNo)
    }else if(mode === "WARN"){
        console.warn(initialMessage,errorMessage + "at line:" + lineNo)
    }else{
        throw "Wrong mode"
    }
}

当团队中的任何开发者需要向控制台打印 Stats.js 文件中的错误时,可以用如下方式使用函数

loggerHelper("ERROR","Error At Stats.js","Invalid argument passed",23)
loggerHelper("ERROR","Error At Stats.js","undefined argument",223)
loggerHelper("ERROR","Error At Stats.js","curry function is not defined",3)
loggerHelper("ERROR","Error At Stats.js","slice is not defined",31)

现在我们可以用 curry 函数重写这个函数了,下面通过 curry 解决重复使用前两个参数的问题

let errorLogger = curry(loggerHelper)("ERROR")("Error At Stats.js")
let debugLogger = curry(loggerHelper)("DEBUG")("Debug At Stats.js")
let warnLogger = curry(loggerHelper)("WARN")("Warn At Stats.js")

现在我们能够轻松使用上面的柯里化函数并在各自的上下文中使用它们了

// 用于错误
errorLogger("Error message",21)
// Error At Stats.js Error message at line:21

// 用于调试
debugLogger("Debug message",223)
// Debug At Stats.js Debug message at line:223

// 用于警告
warnLogger("Warn message",34)
// Warn At Stats.js Warn message at line:223

经过这个过程我们发现,柯里化能够应对更加复杂的逻辑封装。当情况变得多变,柯里化依然能够应付自如。

函数的隐式转换
当我们直接将函数参与其他的计算时,函数会默认调用toString方法,直接将函数体转换为字符串参与计算

function fn() { return 20 }
console.log(fn + 10);     // 输出结果 function fn() { return 20 }10

但是我们可以重写函数的toString方法,让函数参与计算时,输出我们想要的结果

function fn() { return 20; }
fn.toString = function() { return 20 }
console.log(fn + 10); // 30

除此之外,当我们重写函数的valueOf方法也能够改变函数的隐式转换结果。当我们同时重写函数的toString方法与valueOf方法时,最终的结果会取valueOf方法的返回结果。


2.Partial Application(偏函数)

在计算机科学中,局部应用是指固定一个函数的一些参数,然后产生另一个更小元的函数

偏函数与柯里化区别:
  (1)柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数
  (2)局部应用则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数

柯里化可以看做偏函数的一种特殊的应用

有这样的一个场景:我们需要对多个不同的接口发起HTTP请求

function ajax(url, data, callback) {
  // ..
}

如果直接调用该函数,每一次调用都很麻烦。我们可能产生如下调用方式:

function ajaxTest1(data, callback) {
  ajax('http://www.test.com/test1', data, callback);
}

function ajaxTest2(data, callback) {
  ajax('http://www.test.com/test2', data, callback);
}

我们通过ajaxTest1()把原函数ajax()的参数个数从3个减少到了2个

利用偏函数,我们可以这样做,我们这样定义一个partial()函数:

function partial(fn, ...presetArgs) {
  return function partiallyApplied(...laterArgs) {
        let allArgs =presetArgs.concat(laterArgs)
        return fn.apply(this, allArgs)
  }
}

partial()函数接收fn参数,来表示被我们偏应用实参(partially apply)的函数。接着,fn形参之后,presetArgs数组收集了后面传入的实参,保存起来稍后使用。

我们创建并return了一个新的内部函数,该函数中,laterArgs数组收集了全部实参。
使用偏函数的这种模式,我们重构之前的代码:

function ajax(url, data, callback) {
  // ..
}

var ajaxTest1 = partial(ajax, 'http://www.test.com/test1');
var ajaxTest2 = partial(ajax, 'http://www.test.com/test2');


3.柯里化或偏函数有什么用?

柯里化或偏函数主要是对于参数进行一些操作,将多个参数转换为单一参数或者减少参数个数的过程。如果参数不足的话它们就会处在一种中间状态,我们可以利用这种中间状态做任何事!!!而传统函数调用则需要预先确定所有实参。如果你在代码某一处只获取了部分实参,然后在另一处确定另一部分实参,这个时候柯里化和偏应用就能派上用场。

归纳一下:

(1)函数更加灵活,粒度更小
(2)函数参数缓存,延迟计算


参考

JavaScript函数柯里化

你可能感兴趣的:(JavaScript 函数柯里化和偏函数)