點燈坊

失くすものさえない今が強くなるチャンスよ

自行實作 pipe() 組合 Function

Sam Xiao's Avatar 2020-03-07

pipe() 是 FP 最重要 Function 之一,其中 Function Pipeline 就是由 pipe() 展開,實務上都是直接使用 Ramda 的 pipe(),事實上也能自行以 ECMAScript 的 reduce() 實現。

Version

Ramda 0.27.0
Wink-fp 1.20.47

Ramda

import { pipe, filter, map } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = v => pipe(
  filter(x => x.price === v),
  map(x => x.title)
)

f(300)(data); // ?

實務上實現 Function Pipeline 時,會使用 Ramda 的 pipe()

pipe000

reduce()

import { filter, map } from 'ramda'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let pipe = (...fs) => v => fs.reduce((g, f) => f(g), v) 

let f = v => pipe(
  filter(x => x.price === v),
  map(x => x.title),
)

f(300)(data) // ?

第 1 行

import { filter, map } from 'ramda'

沒使用 Ramda 所提供的 pipe()

第 9 行

let pipe = (...fs) => v => fs.reduce((g, f) => f(g), v) 

由於 pipe() 是由左至右,因此使用 reduce()

(...fs)

使用 ES6 的 rest parameter,表示 pipe() 的 argument 個數無限,且 fs 為 array,可大膽使用 Array.prototype 下的 reduce()

v => fs.reduce((g, f) => f(g), v)

f 為目前 function,g 為組合後 function,每次會執行 f(g) 組合計算結果。

因為結果仍是 function,所以為 v => fns.reduce()

pipe001

Wink-fp

import { filter, map } from 'ramda'
import { pipe } from 'wink-fp'

let data = [
  { title: 'FP in JavaScript', price: 100 },
  { title: 'RxJS in Action', price: 200 },
  { title: 'Speaking JavaScript', price: 300 }
]

let f = v => pipe(
  filter(x => x.price === v),
  map(x => x.title),
)

f(300)(data) // ?

亦可改用 Wink-fp 所提供的 pipe(),結果完全相同。

pipe()
(((a, b, …, n) → o), (o → p), …, (x → y), (y → z)) → ((a, b, …, n) → z)
將 function 由左至右組合成新 function

pipe002

Conclusion

  • pipe() 一直被認為是黑魔法,事實上只是將 function 加以 reduce() 而已,能自己寫過一次,就不會覺得那麼遙不可及了

Reference

Yazeed Bzadough, 10 More Utility Functions Made with Reduce
MDN, Rest parameters
MDN, reduce()
Ramda, pipe()