
用 JS 理解柯里化

函数式编程风格,试图以函数作为参数传递(回调)和无副作用的返回函数(修改程序的状态)。
很多语言采用了这种编程风格。JavaScript,Haskell,Clojure,Erlang 和 Scala 是其中最受欢迎的几种语言。
数式编程风格具有传递和返回函数的能力,它带来了许多概念:
- Pure Functions(纯函数)
- Currying(柯里化)
- Higher-Order functions(高阶函数)
我们将在这里介绍这些概念中的一种 Currying。
在本文中,我们将看到 currying 如何工作,以及它如何在软件开发人员的工作中发挥作用。
提示:您可以将其变为 Bit组件,而不是复制粘贴可重用的 JS 功能,并快速与团队共享项目。
Bit - 使用代码组件共享和构建
Bit 可帮助您在项目和应用程序之间共享,发现和使用代码组件,以构建新功能和... bitsrc.io
什么是 Currying?
Currying 是函数式编程中的一个过程,我们可以将具有多个参数的函数转换为顺序嵌套的函数。它返回一个接收下一个参数的新函数。
它不断返回一个新函数(接收当前参数,就像我们之前所说的那样),直到所有参数都用完为止。保留参数 "alive"
(通过闭包),并且当返回并执行 currying 链中的最终函数时,所有参数都在执行中使用。
Currying 是将具有多个 arity 的函数转换为具有较少 arity 的函数的过程 - Kristina Brainwave
注意:术语 arity
“是指函数所接收的参数个数”。例如,
function fn (a, b) {
// ...
}
function _fn(a, b, c) {
// ...
}
函数 fn
接受两个参数(2-arity 函数),_fn
接受三个参数(3-arity 函数)。
因此,currying 将具有多个参数的函数转换为一系列函数,每个函数都接收一个参数。
我们来看一个简单的例子:
function multiply(a, b, c) {
return a * b * c;
}
此函数接收三个数字,将数字相乘并返回结果。
multiply(1, 2, 3) // 6
请参阅我们如何使用完整参数调用 multiply 函数,来创建一个 curried
版本函数,看看我们如何在一系列调用中调用相同的函数(并得到相同的结果):
function multiply(a) {
return (b) => {
return (c) => {
return a * b * c;
}
}
}
log(multiply(1)(2)(3)) // 6
我们已将 multiply(1,2,3)
函数调用转为 multiply(1)(2)(3)
多个函数调用。
我们已经将一个函数转为一系列函数。为了得到三个数相乘的结果 1
,2
和 3
三个数字一个接一个地传递,每个数字都将在下一个函数的内部调用。我们可以将 multiply(1)(2)(3)
分开以便更好地理解它:
const mul1 = multiply(1);
const mul2 = mul1(2);
const result = mul2(3);
log(result); // 6
让我们一个接一个地接收参数。我们把 1
传递给了 multiply
函数:
let mul1 = multiply(1);
它返回这个函数:
return (b) => {
return (c) => {
return a * b * c;
}
}
现在,mul1
保持上面的函数定义,它带有一个参数 b
。
我们调用了 mul1
函数,传入 2
:
let mul2 = mul1(2);
在 mul2
中将返回第三个函数:
return (c) => {
return a * b * c;
}
返回的函数现在存储在 mul2
变量中。
从本质上讲,mul2
将是:
mul2 = (c) => {
return a * b * c;
}
当 mul2
以 3
作为参数调用时,
const result = mul2(3);
它把之前传入的参数 a = 1
,b = 2
带入计算并返回结果 6
。
log(result); // 6
作为嵌套函数,mul2
可以访问外部函数 multiply
以及 mul1
作用域内的变量。
这就是为什么 mul2
能使用已经在的函数中定义的变量并执行乘法运算的原因。虽然这些函数早已在内存中被回收 garbage collected
,但它的变量仍以某种方式保留 "alive"
。
您会看到三个数字一次一个地应用于该函数,并且每次都返回一个新函数,直到所有数字都用完为止。
让我们看另一个例子:
function volume(l, w, h) {
return l * w * h;
}
const aCylinder = volume(100, 20, 90); // 180000
我们有一个计算实心物体体积的函数 volume
。
柯里化后的版本将接受一个参数并返回一个函数,该函数也将接收一个参数并返回一个函数。这个过程将被循环/继续,直到到达最后一个参数并返回最后一个函数,这将执行与前一个参数和最后一个参数的乘法运算。
function volume(l) {
return (w) => {
return (h) => {
return l * w * h;
}
}
}
const aCylinder = volume(100)(20)(90) // 180000
与我们在 multiply
函数中所使用的一样,最后一个函数只接受 h
,但是它将执行的操作所用到的其他函数作用域内的变量早已被函数返回。因为闭包 Closure ,这些参数仍然有效。
currying 背后的想法是接收一个函数并返回一个特殊函数的函数。
数学中的 Currying
我有点喜欢数学例证