(翻译)JavaScript函数式编程中的pipe()与componse()

237 阅读3分钟

前言

函数式编程对我来说是一次大开眼界的旅程。这篇文章,以及类似的文章,是我在探索新的函数式编程领域时,尝试分享我的见解和观点。

Ramda一直是我常用的FP库,因为它让JavaScript的函数式编程变得更加简单。我强烈推荐它。

Image for post

Pipe

pipe(管道)的概念是很简单的——它组合了几个函数。pipe 是一个从左到右流动的管道,用上一个函数的输出调用每个函数。

getName = (person) => person.name
getName({ name: 'Buckethead' })
// 'Buckethead'

让我们写一个函数,让字符串变为大写。

uppercase = (string) => string.toUpperCase()
uppercase('Buckethead')
// 'BUCKETHEAD'

如果我们想得到person对象的name,并且将其转为大写,我们可以这样写:

name = getName({ name: 'Buckethead' })
uppercase(name)
// 'BUCKETHEAD'

这很好,但我们还是去掉那个中间变量名吧。

uppercase(getName({ name: 'Buckethead' }))

很多了,但不喜欢这种嵌套的方式。如果我们想添加一个获取字符串前6个字符的函数呢?

get6Characters = (string) => string.substring(0, 6)
get6Characters('Buckethead')
// 'Bucket'

结果是这样的:

get6Characters(uppercase(getName({ name: 'Buckethead' })))
'BUCKET'

让我们再添加一个函数来反转字符串。

reverse = (string) => string
  .split('')
  .reverse()
  .join('')
reverse('Buckethead')
// 'daehtekcuB'

现在我们看到的是这个样子:

reverse(get6Characters(uppercase(getName({ name: 'Buckethead' }))))
// 'TEKCUB'

它可能会变得有点太...多。

pipe来拯救我们!

取代嵌套函数或者创造一连串的中间变量吧,用pipe可以做到所有的事情!

pipe(
  getName,
  uppercase,
  get6Characters,
  reverse 
)({ name: 'Buckethead' })
// 'TEKCUB'

简约的艺术。看起来就像一个待办事项列表!

让我们一步步走过去。

出于演示的目的,我将使用使用Eric Elliott写的functional programming articles文章中的管道实现。

pipe = (...fns) => x => fns.reduce((v, f) => f(v), x)

我喜欢这简短的一行代码。

我们将使用rest参数(剩余参数),可以看我的这篇文章,我们会使用pipe组合n个函数。每一个函数都会取前一个函数的输出,并且都会简化为一个值。

你可以像我们上面这样使用它。

pipe(
  getName,
  uppercase,
  get6Characters,
  reverse 
)({ name: 'Buckethead' })
// 'TEKCUB'

我将扩展pipe,并且打几个断点,一行一行的看。

pipe = (...functions) => (value) => {
  debugger;
  return functions
    .reduce((currentValue, currentFunction) => {
       debugger;
       return currentFunction(currentValue);
    }, value)
}

Image for post

用我们的例子调用pipe,让奇妙的事情发生。

Image for post

查看本地变量,functions是包含四个函数的数组,value的值是{ name: 'Buckethead' }

现在使用rest参数(再说一次, 这里 😁可以看我相关的文章哈),pipe允许我们使用任意数量的函数,它将循环调用每一个。

Image for post

下一个断点中,我们内部使用了reduce,在这个函数中,currentValue传入currentFunction中并且返回。

我们看到结果是Buckethead,因为当前的currentFunction(getName)返回的是任意对象的name属性。这个值将会reduce中返回,下次会变成新的currentValue。让我们继续打断点看一下。

Image for post

现在currentValuesBuckethead,因为上次返回的就是这个值。当前currentFunctionuppercase,所以BUCKETHEAD将会成为下一个currentValue

Image for post

类似的,截取BUCKETHEAD的前六个字母,并将他们放入下一个函数中。

Image for post

reverse(‘.aedi emaS’)

Image for post

你已经完成了!

什么是compose()呢?

区别只是与pipe的方向不同。

所以如果你想和上面的pipe一样得到相同的结果,你应该将函数的顺序反过来。

compose(
  reverse,
  get6Characters,
  uppercase,
  getName,
)({ name: 'Buckethead' })

注意到getName是在链中的最后一个而reverse是在第一个了吗?

这里有一个componse的快速实现,同样出自神奇的Eric Elliott同一篇文章.

compose = (...fns) => x => fns.reduceRight((v, f) => f(v), x);

这个扩展的函数断点练习留给你,玩一玩,用一用,欣赏一下。最重要的,乐在其中!

原文链接

A quick introduction to pipe() and compose() in JavaScript