函数组合
函数组合
高阶函数
函数式编程倾向于重用一系列公共的纯函数来处理数据,而面向对象编程则是将方法与数据封装到对象内。这些被封装起来的方法复用性不强,只能作用于某些类型的数据,往往只能处理所属对象的实例这种数据类型。而函数式编程中,任何类型的数据则是被一视同仁,譬如map()
函数允许开发者传入函数参数,保证其能够作用于对象、字符串、数字,以及任何其他类型。JavaScript 中函数同样是一等公民,即我们可以像其他类型一样处理函数,将其赋予变量、传递给其他函数或者作为函数返回值。而高阶函数(Higher Order Function )则是能够接受函数作为参数,能够返回某个函数作为返回值的函数。高阶函数经常用在如下场景:
- 利用回调函数、Promise 或者 Monad 来抽象或者隔离动作、作用以及任何的异步控制流
- 构建能够作用于泛数据类型的工具函数
- 函数重用或者创建柯里函数
- 将输入的多个函数并且返回这些函数复合而来的复合函数
典型的高阶函数的应用就是复合函数,作为开发者,我们天性不希望一遍一遍地重复构建、测试与部分相同的代码,我们一直在寻找合适的只需要写一遍代码的方法以及如何将其重用于其他模块。代码重用听上去非常诱人,不过其在很多情况下是难以实现的。如果你编写过于偏向具体业务的代码,那么就会难以重用。而如果你把每一段代码都编写的过于泛化,那么你就很难将这些代码应用于具体的有业务场景,而需要编写额外的连接代码。而我们真正追寻的就是在具体与泛化之间寻求一个平衡点,能够方便地编写短小精悍而可复用的代码片,并且能够将这些小的代码片快速组合而解决复杂的功能需求。在函数式编程中,函数就是我们能够面向的最基础代码块,而在函数式编程中,对于基础块的组合就是所谓的函数复合(Function Composition )。我们以如下两个简单的 JavaScript 函数为例
const add10 = function (value) {
return value + 10;
};
const mult5 = function (value) {
return value * 5;
};
如果你习惯了使用 ES6,那么可以用 Arrow Function 重构上述代码
const add10 = (value) => value + 10;
const mult5 = (value) => value * 5;
现在看上去清爽多了吧,下面我们考虑面对一个新的函数需求,我们需要构建一个函数,首先将输入参数加 10 然后乘以 5,我们可以创建一个新函数如下
const mult5AfterAdd10 = (value) => 5 * (value + 10);
尽管上面这个函数也很简单,我们还是要避免任何函数都从零开始写,这样也会让我们做很多重复性的工作。我们可以基于上文的 add10 与 mult5 这两个函数来构建新的函数
const mult5AfterAdd10 = (value) => mult5(add10(value));
在 mult5AfterAdd10 函数中,我们已经站在了 add10 与 mult5 这两个函数的基础上,不过我们可以用更优雅的方式来实现这个需求。在数学中,我们认为f ∘ g
是所谓的 Function Composition,因此``f ∘ g可以认为等价于
f(g(x))`,我们同样可以基于这种思想重构上面的 mult5AfterAdd10。不过 JavaScript 中并没有原生的 Function Composition 支持,在 Elm 中我们可以用如下写法
add10 value =
value + 10
mult5 value =
value * 5
mult5AfterAdd10 value =
(mult5 << add10) value
这里的<<
操作符也就指明了在 Elm 中是如何组合函数的,同时也较为直观的展示出了数据的流向。首先 value 会被赋予给 add10,然后 add10 的结果会流向 mult5。另一个需要注意的是,(mult5 << add10)
中的中括号是为了保证函数组合会在函数调用之前。你也可以组合更多的函数
f x =
(g << h << s << r << t) x
如果在 JavaScript 中,你可能需要以如下的递归调用来实现该功能
g(h(s(r(t(x)))))