JavaScript 函数式编程技巧 - 柯里化
00 分钟
2022-2-23
2023-7-20
category
tags
type
status
slug
date
summary
icon
password
转载自 freecodecamp
 
作为函数式编程语言,JS 带来了很多语言上的有趣特性,比如柯里化和反柯里化。

1. 简介

柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。
核心思想是把多参数传入的函数拆成单参数(或部分)函数,内部再返回调用下一个单参数(或部分)函数,依次处理剩余的参数。
按照 Stoyan Stefanov --《JavaScript Pattern》作者 的说法,所谓“柯里化”就是使函数理解并处理部分应用
柯里化有3个常见作用:
  1. 参数复用
  1. 提前返回
  1. 延迟计算/运行
talk is cheap,看看怎么实现吧。

2. 实现

2.1 通用实现

一个通用实现:
注意这里 concat 接受非数组元素参数将被当做调用者的一个元素传入
用它将一个 sayHello 函数柯里化试试:
嘻嘻,感觉还行。

2.2 高阶柯里化函数

以上柯里化函数已经能解决一般需求了,但是如果要多层的柯里化总不能不断地进行 currying 函数的嵌套吧,我们希望经过柯里化之后的函数每次只传递一个或者多个参数,那该怎么做呢:
如此实现一个高阶的柯里化函数,使得柯里化一个函数的时候可以不用嵌套的 currying,当然是因为把嵌套的地方放到了 curryingHelper 里面进行了...-。-

2.3 疯狂柯里化函数

尽管柯里化函数已经很牛了,但是它也让你必须花费点小心思在你所定义函数的参数顺序上。在一些函数式编程语言中,会定义一个特殊的“占位变量”。通常会指定下划线来干这事,如果作为一个函数的参数被传入,就表明这个是可以“跳过的”,是尚待指定的参数。比如:
JS不具备这样的原生支持,可以使用一个全局占位符变量const _ = { }并且通过===来判断是否是占位符,当然你如果使用了lodash的话可以使用别的符号代替。那么可以这样改造柯里化函数:

3. 柯里化的常见用法

3.1 参数复用

通过柯里化方法,缓存参数到闭包内部参数,然后在函数内部将缓存的参数与传入的参数组合后 apply/bind/call 给函数执行,来实现参数的复用,降低适用范围,提高适用性。
参看以下栗子,官员无论添加后续老婆,都能和合法老婆组合,通过柯里化方法, getWife 方法就无需添加多余的合法老婆。

3.2 提高适用性

通用函数解决了兼容性问题,但同时也会再来,使用的不便利性,不同的应用场景往,要传递很多参数,以达到解决特定问题的目的。有时候应用中,同一种规则可能会反复使用,这就可能会造成代码的重复性。
同一规则重复使用,带来代码的重复性,因此可以使用上面的通用柯里化实现改造一下:
可以看到这里柯里化方法的使用和偏函数比较类似,顺便回顾一下偏函数。
偏函数是创建一个调用另外一个部分(参数或变量已预制的函数)的函数,函数可以根据传入的参数来生成一个真正执行的函数,比如:
这样就用偏函数快速创建了一组判断对象类型的方法。
偏函数固定了函数的某个部分,通过传入的参数或者方法返回一个新的函数来接受剩余的参数,数量可能是一个也可能是多个 柯里化是把一个有n个参数的函数变成n个只有1个参数的函数,例如:add = (x, y, z) => x + y + zcurryAdd = x => y => z => x + y + z当偏函数接受一个参数并且返回了一个只接受一个参数的函数,与两个接受一个参数的函数curry()()的柯里化函数,这时候两个概念类似。(个人理解不知道对不对)

3.3 延迟执行

柯里化的另一个应用场景是延迟执行。不断的柯里化,累积传入的参数,最后执行。例如累加:
更通用的写法,将处理函数提取出来:

4. Function.prototype.bind 方法也是柯里化应用

与 call/apply 方法直接执行不同,bind 方法将第一个参数设置为函数执行的上下文,其他参数依次传递给调用方法(函数的主体本身不执行,可以看成是延迟执行),并动态创建返回一个新的函数,这符合柯里化特点。
下面是一个 bind 函数的模拟,testBind 创建并返回新的函数,在新的函数中将真正要执行业务的函数绑定到实参传入的上下文,延迟执行了。