函数式编程是一种程序开发样式,其中广泛使用一些用于函数的特定功能。 特别是关于将函数作为参数传递给其他函数,以及关于从其他函数返回函数。 “纯函数”的概念也属于编程的函数样式。 纯函数的输出仅取决于输入,它们在执行时不会影响程序的状态。
功能编程原理受许多语言支持。 其中包括JavaScript,Haskell,Clojure,Erlang。 功能编程机制的使用尤其意味着对概念的了解,例如纯函数,柯里化函数,高阶函数。

我们今天翻译的材料是关于curring的。 我们将讨论currying的工作原理,以及这种机制的知识如何对JS开发人员有用。
什么是咖喱?
函数式编程中的
咖喱化是将具有多个参数的函数转换为一组具有一个参数的嵌套函数。 当调用带有一个参数的咖喱函数时,它将返回一个新函数,该函数期望下一个参数到达。 每次调用curried函数时,都会返回等待下一个参数的新函数-直到该函数收到所需的所有参数为止。 得益于闭包机制,先前获得的参数正在等待该函数获得执行计算所需的一切的那一刻。 收到最后一个参数后,函数将执行计算并返回结果。
说到
curring ,我们可以说这是将具有多个参数的函数转换为具有较少Arity的函数的过程。
Arity是函数的参数数量。 例如,这是一对函数的声明:
function fn(a, b) { //... } function _fn(a, b, c) { //... }
fn
函数带有两个参数(这是一个二进制或二进制函数),
_fn
函数
_fn
三个参数(一个三进制,三进制函数)。
让我们谈谈在currying期间将具有多个参数的函数转换为一组函数,每个函数都带有一个参数的情况。
考虑一个例子。 我们具有以下功能:
function multiply(a, b, c) { return a * b * c; }
它接受三个参数并返回其乘积:
multiply(1,2,3);
现在让我们考虑如何将其转换为一组函数,每个函数都有一个参数。 让我们为该函数创建一个咖喱版,看看调用多个函数时如何获得相同的结果:
function multiply(a) { return (b) => { return (c) => { return a * b * c } } } log(multiply(1)(2)(3))
如您所见,此处我们将调用转换为具有三个参数的单个函数-
multiply(1,2,3)
转换为对三个函数的调用-
multiply(1)(2)(3)
。
事实证明,一个功能已变成多个功能。 使用新构造时,除最后一个函数外,每个函数都将返回计算结果,并接受一个参数并返回另一个函数,该函数也可以接受一个参数并返回另一个函数。 如果您对
multiply(1)(2)(3)
的形式的构造不太清楚,请以这种形式编写它,以更好地理解这一点:
const mul1 = multiply(1); const mul2 = mul1(2); const result = mul2(3); log(result);
现在,让我们逐行讨论这里发生的事情。
首先,我们将参数
1
传递给
multiply
函数:
const mul1 = multiply(1);
当此功能起作用时,此设计起作用:
return (b) => { return (c) => { return a * b * c } }
现在
mul1
引用了一个带有参数
b
的函数。 我们调用函数
mul1
,将其传递给
2
:
const mul2 = mul1(2);
作为此调用的结果,将执行以下代码:
return (c) => { return a * b * c }
mul2
将包含对其中可能包含的函数的引用,例如,由于以下操作:
mul2 = (c) => { return a * b * c }
如果现在调用函数
mul2
并将其传递
3
,则该函数将使用参数
a
和
b
执行必要的计算:
const result = mul2(3);
这些计算的结果将是
6
:
log(result)
具有最高嵌套级别的
mul2
函数可以访问范围,可以访问由
multiply
和
mul1
形成的闭包。 这就是为什么在
mul2
函数
mul2
可以使用在已经完成执行的函数中声明的变量执行计算的原因,这些变量已经返回一些值并由垃圾回收器处理。
上面我们检查了一个抽象示例,但从本质上讲,它是用于计算矩形框体积的相同函数。
function volume(l,w,h) { return l * w * h; } const vol = volume(100,20,90)
其咖喱版本如下所示:
function volume(l) { return (w) => { return (h) => { return l * w * h } } } const vol = volume(100)(20)(90)
因此,currying基于以下思想:基于某个功能,将创建另一个返回专用功能的功能。
咖喱和部分使用功能
现在,也许会有一种感觉,当将一个函数表示为一组嵌套函数时,嵌套函数的数量取决于该函数的参数数量。 如果涉及到计算,那就是。
我们已经看到,可以使用以下特殊形式来计算体积:
function volume(l) { return (w, h) => { return l * w * h } }
在这里应用了思想,与上面讨论的思想非常相似。 您可以按以下方式使用此功能:
const hV = volume(70); hV(203,142); hV(220,122); hV(120,123);
您可以这样做:
volume(70)(90,30)
实际上,在这里您可以看到我们如何使用
volume(70)
命令创建一个专门的函数来计算物体的体积,该物体的尺寸之一(即length,
l
)是固定的。 与以前版本的类似函数不同,
volume
函数需要3个参数并包含2个嵌套函数,而该函数的当前版本包含3个嵌套函数。
在调用
volume(70)
之后获得的功能实现了部分功能应用程序的概念。 函数的Currying和部分应用彼此非常相似,但是概念不同。
在部分应用程序中,该函数将转换为带有较少参数(较少Arity)的另一个函数。 此类函数的某些参数是固定的(已为其设置默认值)。
例如,有一个这样的功能:
function acidityRatio(x, y, z) { return performOp(x,y,z) }
可以将其转换为:
function acidityRatio(x) { return (y,z) => { return performOp(x,y,z) } }
此处未提供
performOp()
函数的实现,因为它不影响所考虑的概念。
可以通过使用需要固定其值的参数调用新的
acidityRatio()
函数而获得的函数是原始函数,该函数的其中一个参数是固定的,并且该函数本身比原始函数少一个参数。
该函数的当前版本将如下所示:
function acidityRatio(x) { return (y) = > { return (z) = > { return performOp(x,y,z) } } }
如您所见,在进行currying时,嵌套函数的数量等于原始函数的参数数量。 这些函数中的每个函数都有自己的参数。 显然,如果参数的功能不接受或仅接受一个参数,则无法进行处理。
在一个函数有两个参数的情况下,可以说它的currying和部分应用的结果是一致的。 例如,我们有这样一个功能:
function div(x,y) { return x/y; }
假设我们需要重写它,以便我们可以在固定第一个参数的情况下,获得仅在将第二个参数传递给它时执行计算的函数,即,我们需要部分应用此函数。 它看起来像这样:
function div(x) { return (y) => { return x/y; } }
其固化的结果看起来将完全相同。
论递归概念和函数部分应用的实际应用
在各种情况下,函数的咖喱化和部分应用可能会有用。 例如,在开发适合重用的小模块时。
部分使用功能使使用通用模块更加容易。 例如,我们有一个在线商店,其代码中包含一个函数,该函数用于在考虑折扣的情况下计算要支付的金额。
function discount(price, discount) { return price * discount }
有特定类别的客户,我们称它们为“挚爱的客户”,我们给予他们10%的折扣。 例如,如果这样的客户以500美元的价格买了东西,我们给他50美元的折扣:
const price = discount(500,0.10); // $50 // $500 - $50 = $450
很容易注意到,使用这种方法,我们经常需要使用两个参数来调用此函数:
const price = discount(1500,0.10); // $150 // $1,500 - $150 = $1,350 const price = discount(2000,0.10); // $200 // $2,000 - $200 = $1,800 const price = discount(50,0.10); // $5 // $50 - $5 = $45 const price = discount(5000,0.10); // $500 // $5,000 - $500 = $4,500 const price = discount(300,0.10); // $30 // $300 - $30 = $270
原始功能可以简化为允许您以预定折扣级别接收新功能的形式,只需调用该功能就可以转移购买金额。 在我们的示例中,
discount()
函数具有两个参数。 这是我们将其转换成的样子:
function discount(discount) { return (price) => { return price * discount; } } const tenPercentDiscount = discount(0.1);
tenPercentDiscount()
函数是
tenPercentDiscount()
函数部分应用的结果。 调用此函数的
tenPercentDiscount()
,足以传递价格,并且已经设置了10%的
discount
,即
discount
参数:
tenPercentDiscount(500); // $50 // $500 - $50 = $450
如果我们商店中有买家决定给予20%的折扣,那么您可以像下面这样获得适当的功能来与他们合作:
const twentyPercentDiscount = discount(0.2);
现在,可以考虑到20%的折扣来调用二十%
twentyPercentDiscount()
函数来计算商品成本:
twentyPercentDiscount(500); // 100 // $500 - $100 = $400 twentyPercentDiscount(5000); // 1000 // $5,000 - $1,000 = $4,000 twentyPercentDiscount(1000000); // 200000 // $1,000,000 - $200,000 = $600,000
通用功能,可部分应用其他功能
我们将开发一个接受任何函数并返回其变体的函数,该函数是一个已经设置了一些参数的函数。 这是允许您执行此操作的代码(如果您打算开发类似的功能,则很可能会得到其他结果):
function partial(fn, ...args) { return (..._arg) => { return fn(...args, ..._arg); } }
partial()
函数接受我们要转换为部分应用函数的
fn
函数,以及可变数量的参数
(...args
)。
rest
语句用于将
fn
之后的所有参数放入
args
。
该函数返回另一个函数,该函数也接受可变数量的参数(
_arg
)。 该函数依次调用原始
fn
函数,并向其传递参数
...args
和
..._arg
(使用
spread
运算符)。 该函数执行计算并返回结果。
我们使用此函数来创建您已经熟悉的
volume
函数的变体,用于计算长方体的体积,长方体的边之一是固定的:
function volume(l,h,w) { return l * h * w } const hV = partial(volume,100); hV(200,900); // 18000000 hV(70,60); // 420000
在这里,您可以找到用于通用其他功能的通用功能示例。
总结
在本文中,我们讨论了函数的递归和部分应用。 由于闭包以及JS中的函数是第一类的对象(它们可以作为参数传递给其他函数,从它们返回并分配给变量)的事实,这些用于转换函数的方法在JavaScript中得以实现。
亲爱的读者们! 您是否在项目中使用弯曲技术和部分功能应用?
