JavaScript中使用函数组合
Last updated
Last updated
前提条件:我在本文中使用柯里化,所以如果你不知道这货,我推荐你先阅读我上一个文章:JavaScript中的柯里化
译者:关于柯里化,译者推荐你阅读这一篇curry。当然,如果你对函数式编程的概念也模糊,推荐在看下这一篇什么是函数式编程
函数组合是将多个简单函数组合在一起以构建更复杂的函数的机制。每个函数的结果都传递给下一个函数。在数学中,我们会经常这样写: f(g(x))
。所以这个g(x)
的结果被传递了f
。在编程中,我们可以通过写类似的东西来实现组合。
让我们举一个简单的例子。
假设我需要做如下运算来做一些算术: 2 + 3 * 5
。正如你所知的那样,乘法计算优先于加法运算。所以你先计算3 * 5
,然后在结果中加2
。让我们用JavaScript来写。最主要、当然也是最简单的方法可能是:
这是函数组合的一种形式,这是把乘法的结果传递给add
函数。让我们更进一步,看看另一个案例,在这里函数组合显得非常有用。现在假设我有一个用户列表,我需要提取所有成人用户的名字。我个人会这样写:
这很好,但是如果我们能够自动化组合,效果会更好。至少它可能更具可读性。
所以我们在本节中的目标是创建一个高阶函数,它接受两个或多个函数并组合它们。那么就让我们来定义我们未来函数的最终签名:
译者: 术语: 函数签名
例如,我们想这样调用函数:
所以这样一个函数的实现可以是这样:
那不是很棒吗?这个只有一行的函数允许你组合任何函数来构建复杂的转换。让我解释一下这里发生的事情:
compose
是一个高阶函数。它是一个返回另一个函数的函数。
compose
携带多个函数作为参数,我们使用扩展运算符:...
将函数集转为数组。
然后我们在这些函数上简单的应用了reduceRight
。回调的第一个参数是当前参数。第二个参数是当前的函数。然后我们调用每个函数时携带当前参数,其结果用于下一个调用。
现在我们可以在上个例子里使用这个函数了。改造之后会更具可读性:
我给你们举最后一个例子。让我们实现传统的MapReduce。
MapReduce的原理很简单。它只是在一组数据上应用map
并reduce
结果以产生单个结果。这通常是函数组合的原理。因此,例如,我们可以实现传统的单词计数器来计算多个单词。当map
遇到一个值时,它只负责发送1
,reduce
将对最终数组进行求和,生成结果:
我添加了这部分,因为Yeiber Cano提到我第一次实现是pipe
而不是compose
。你可以在本文下方阅读他的评论。
Great writing Rémi +1,
One observation, for
f(g(x))
signature to hold true, compose may needreduceRight
instead. Composition starts from right to left:
因此,compose
和pipe
之间的主要区别在于组合的顺序。组合执行从右到左的函数组合,因为管道执行从左到右的组合。我们来写一下管道的高阶函数:
所以在这种情况下,我们使用reduce
而不是reduceRight
来从左到右执行组合。
然后,我们可以将新创建的函数应用于前面的示例:
有些人更喜欢使用pipe
而不是compose
,因为他们发现它更具可读性。至少,我们都同意这样更自然!
这是一个简单的例子,但是要注意,你可以在更复杂的例子中使用它。函数组合在大多数函数库(如lodash或ramda)上都有实现。你甚至可以找到函数组合的变种。例如,Ramda提出了一个composeP
函数,允许你组合返回promise的函数: http://ramdajs.com/docs/#composeP
译者:函数组合和函数管道的区别就是组合的顺序。
pipe
执行的是从左到右(reduce
),`compose
执行的是从右到左(reduceRight)。