10

一文讲懂什么是函数柯里化,柯里化的目的及其代码实现

 2 years ago
source link: https://www.fly63.com/article/detial/12084
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

柯里化(Currying)

柯里化(Currying)是一种关于函数的高阶技术。它不仅被用于 JavaScript,还被用于其他编程语言。

柯里化是一种函数的转换,它是指将一个函数从可调用的 f(a, b, c) 转换为可调用的 f(a)(b)(c) 。

柯里化不会调用函数。它只是对函数进行转换。

让我们先来看一个例子,以更好地理解我们正在讲的内容,然后再进行一个实际应用。

我们将创建一个辅助函数 curry(f) ,该函数将对两个参数的函数 f 执行柯里化。换句话说,对于两个参数的函数 f(a, b) 执行 curry(f) 会将其转换为以 f(a)(b) 形式运行的函数:

function curry(f) { // curry(f) 执行柯里化转换
return function(a) {
return function(b) {
return f(a, b);
};
};
}

// 用法
function sum(a, b) {
return a + b;
}

let curriedSum = curry(sum);

alert( curriedSum(1)(2) ); // 3

正如你所看到的,实现非常简单:只有两个包装器(wrapper)。

  • curry(func) 的结果就是一个包装器 function(a) 。
  • 当它被像 curriedSum(1) 这样调用时,它的参数会被保存在词法环境中,然后返回一个新的包装器 function(b) 。
  • 然后这个包装器被以 2 为参数调用,并且,它将该调用传递给原始的 sum 函数。

柯里化更高级的实现,例如 lodash 库的 _.curry ,会返回一个包装器,该包装器允许函数被正常调用或者以偏函数(partial)的方式调用:

function sum(a, b) {
return a + b;
}

let curriedSum = _.curry(sum); // 使用来自 lodash 库的 _.curry

alert( curriedSum(1, 2) ); // 3,仍可正常调用
alert( curriedSum(1)(2) ); // 3,以偏函数的方式调用

柯里化?目的是什么?

要了解它的好处,我们需要一个实际中的例子。

例如,我们有一个用于格式化和输出信息的日志(logging)函数 log(date, importance, message) 。在实际项目中,此类函数具有很多有用的功能,例如通过网络发送日志(log),在这儿我们仅使用 alert :

function log(date, importance, message) {
alert(`[${date.getHours()}:${date.getMinutes()}] [${importance}] ${message}`);
}

让我们将它柯里化!

log = _.curry(log);

柯里化之后, log 仍正常运行:

log(new Date(), "DEBUG", "some debug"); // log(a, b, c)

……但是也可以以柯里化形式运行:

log(new Date())("DEBUG")("some debug"); // log(a)(b)(c)

现在,我们可以轻松地为当前日志创建便捷函数:

// logNow 会是带有固定第一个参数的日志的偏函数
let logNow = log(new Date());

// 使用它
logNow("INFO", "message"); // [HH:mm] INFO message

现在, logNow 是具有固定第一个参数的 log ,换句话说,就是更简短的“偏应用函数(partially applied function)”或“偏函数(partial)”。

我们可以更进一步,为当前的调试日志(debug log)提供便捷函数:

let debugNow = logNow("DEBUG");

debugNow("message"); // [HH:mm] DEBUG message
  1. 柯里化之后,我们没有丢失任何东西: log 依然可以被正常调用。
  2. 我们可以轻松地生成偏函数,例如用于生成今天的日志的偏函数。

高级柯里化实现

如果你想了解更多细节,下面是用于多参数函数的“高级”柯里化实现,我们也可以把它用于上面的示例。

它非常短:

function curry(func) {

return function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
}
}
};

}
function sum(a, b, c) {
return a + b + c;
}

let curriedSum = curry(sum);

alert( curriedSum(1, 2, 3) ); // 6,仍然可以被正常调用
alert( curriedSum(1)(2,3) ); // 6,对第一个参数的柯里化
alert( curriedSum(1)(2)(3) ); // 6,全柯里化

新的 curry 可能看上去有点复杂,但是它很容易理解。

curry(func) 调用的结果是如下所示的包装器 curried :

// func 是要转换的函数
function curried(...args) {
if (args.length >= func.length) { // (1)
return func.apply(this, args);
} else {
return function pass(...args2) { // (2)
return curried.apply(this, args.concat(args2));
}
}
};

当我们运行它时,这里有两个 if 执行分支:

  1. 现在调用:如果传入的 args 长度与原始函数所定义的( func.length )相同或者更长,那么只需要将调用传递给它即可。
  2. func
    pass
    curried
    

例如,让我们看看 sum(a, b, c) 这个例子。它有三个参数,所以 sum.length = 3 。

对于调用 curried(1)(2)(3) :

  1. curried(1)
    1
    pass
    
  2. pass
    (2)
    (1)
    (2)
    curried(1, 2)
    curry
    pass
    
  3. pass
    (3)
    pass(3)
    1
    2
    3
    curried(1, 2, 3)
    3
    

如果这还不够清楚,那你可以把函数调用顺序在你的脑海中或者在纸上过一遍。

只允许确定参数长度的函数

柯里化要求函数具有固定数量的参数。

使用 rest 参数的函数,例如 f(...args) ,不能以这种方式进行柯里化。

比柯里化多一点

根据定义,柯里化应该将 sum(a, b, c) 转换为 sum(a)(b)(c) 。

但是,如前所述,JavaScript 中大多数的柯里化实现都是高级版的:它们使得函数可以被多参数变体调用。

柯里化是一种转换,将 f(a,b,c) 转换为可以被以 f(a)(b)(c) 的形式进行调用。JavaScript 实现通常都保持该函数可以被正常调用,并且如果参数数量不足,则返回偏函数。

柯里化让我们能够更容易地获取偏函数。就像我们在日志记录示例中看到的那样,普通函数 log(date, importance, message) 在被柯里化之后,当我们调用它的时候传入一个参数(如 log(date) )或两个参数( log(date, importance) )时,它会返回偏函数。

现代 JavaScript 教程:开源的现代 JavaScript 从入门到进阶的优质教程。 React 官方文档推荐,与 MDN 并列的 JavaScript 学习教程

在线免费阅读:https://zh.javascript.info

以上文章来源于技术漫谈 ,作者现代前端教程

链接: https://www.fly63.com/article/detial/12084


Recommend

  • 71

    【 js 基础 】【 源码学习 】柯里化和箭头函数

  • 44

    简介 柯里化从何而来 柯里化, 即 Currying 的音译。 Currying 是编译原理层面实现多参函数的一个技术。 在说JavaScript 中的柯里化前,可以聊一下原始的 Currying 是什么,又从何而来。 在编码过程中,身为码农的我们本质上所进行

  • 37
    • 掘金 juejin.im 6 years ago
    • Cache

    三行代码实现 JS 柯里化

    最近有看到一些柯里化的文章,怎么说呢,感觉很奇怪。一篇是阿里云的译文,文章末尾给出了这样一个 "curry": function curry(fn, ...args) { return (..._arg) => { return

  • 38
    • 掘金 juejin.im 6 years ago
    • Cache

    前端之函数柯里化Currying

    什么是柯里化 在计算机科学中,柯里化(Currying)是一种技术(技巧),能够把本来接受 n 个参数的函数A,转换成只接收一个参数的函数B(B中的唯一参数,就是A的多个参数中的 第一个 参数)。 然后新函数B返回的,还是一个函数,记为C(注意原A中返回的不一

  • 8
    • zhuanlan.zhihu.com 4 years ago
    • Cache

    JS函数柯里化

    JS函数柯里化北京奇观技术有限责任公司 软件开发工程师 柯里化(Currying)是把接收多个参数的原函数变换成接受一个单一参数(原来函数的第一个参数的函数)并返回一个新的函...

  • 4

    一文讲懂服务的优雅重启和更新 - kevwan的个人空间 - OSCHINA - 中文开源技术交流社区 在服务端程序更新或重启时,如果我们直接 kill -9 杀掉旧进程并启动新进程,会有以下几个问题: 旧的请求未处理完,如果服务端进程直接...

  • 10
    • lichuanyang.top 3 years ago
    • Cache

    一篇文章讲懂prometheus

    一篇文章讲懂prometheus | Mobility一篇文章讲懂prometheus 发表于 2021-11-10...

  • 6
    • www.fly63.com 2 years ago
    • Cache

    手写JS-柯里化函数的实现

    手写JS-柯里化函数的实现更新日期: 2022-04-29阅读量: 626标签: 

  • 8

    从柯里化讲起,一网打尽 JavaScript 重要的高阶函数 精选 原创 掘金安东尼 2022-1...

  • 8

    JS手撕(四)    call、apply、bind、柯里化、偏函数call()方法就是使用一个指定的this值和一个或多个参数来调用一个函数。 所以原理就是给传入的第一个参数添加临时方法...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK