是否曾經想在 Pipeline 中回傳 null
,表示中斷 Pipeline 呢 ? 透過新的 pipeWith()
則可達成。
Version
macOS Catalina 10.15.3
VS Code 1.43.2
Quokka 1.0.285
Ramda 0.27.0
pipeWith()
import { pipeWith, isNil, unapply } from 'ramda'
let f1 = x => {
if (x === 1) return null
return x + 1
}
let f2 = x => {
if (x === 3) return null
return x + 3
}
let f3 = x => y => x + y
let notNil = (f, x) => isNil(x) ? x : f(x)
let f = pipeWith(notNil)([
f1,
f2,
f3(3)
])
f(1) // ?
f(2) // ?
f(3) // ?
第 3 行
let f1 = x => {
if (x === 1) return null
return x + 1
}
若 input 為 1
則 return null
終止 pipeline,否則繼續 x + 1
。
第 8 行
let f2 = x => {
if (x === 3) return null
return x + 3
}
若由 f1()
計算的結果為 3
則 return null
終止 pipeline,否則繼續 x + 3
。
我們發現
f1()
與f2()
都有終止 pipeline 的需求,這在傳統pipe()
無法實現
17 行
let f = pipeWith(notNil)([
f1,
f2,
f3(3)
])
pipeWith()
的第一個 argument 為 transforming function,當 pipeline 執行第二個之後的 function 前,會先執行該 function,因此可判斷是否為 null
。
pipeWith()
((* → *), [((a, b, …, n) → o), (o → p), …, (x → y), (y → z)]) → ((a, b, …, n) → z)
每個 pipeline 會先經過 transforming function 運算,才會執行下一個 pipeline
* -> *
:transforming function
((* → *), [((a, b, …, n) → o), (o → p), …, (x → y), (y → z)]) → ((a, b, …, n) → z)
:data 為 function array,為要 pipeline 的 function
((a, b, …, n) → z)
:回傳為新 function
15 行
let notNil = (f, x) => isNil(x) ? x : f(x)
notNil()
為 transforming function:
- 第一個 argument 為下一個 pipeline function
- 第二個 argument 為上一個 pipeline function 所回傳值
因此 notNil()
可判斷 null
是否繼續 pipeline。
pipeN()
import { pipeWith, isNil, unapply } from 'ramda'
let f1 = x => {
if (x === 1) return null
return x + 1
}
let f2 = x => {
if (x === 3) return null
return x + 3
}
let f3 = x => y => x + y
let notNil = (f, x) => isNil(x) ? x : f(x)
let pipeN = unapply(pipeWith(notNil))
let f = pipeN(
f1,
f2,
f3(3)
)
f(1) // ?
f(2) // ?
f(3) // ?
pipeWith()
雖然好用,但有兩個問題:
- 每次都要傳入 transforming function
- Data 為 function array,與傳統
pipe()
習慣不一樣
15 行
let notNil = (f, x) => isNil(x) ? x : f(x)
let pipeN = unapply(pipeWith(notNil))
將 pipeWith()
與 notNil()
組合成 pipeN()
,為了維持 pipe()
的 variadic function 特性,特別使用 unapply()
轉換。
19 行
let f = pipeN(
f1,
f2,
f3(3)
)
如此 pipeN()
就可類似 pipe()
的使用方式,且遇到 null
還會終止運算。
pipe() + andThen()
import { pipe, andThen } from 'ramda'
let add = x => y => x + y
let addAsync = x => async y => x + y
let f = pipe(
addAsync(2),
andThen(add(3)),
andThen(addAsync(4))
)
f(1) // ?
第 3 行
let add = x => y => x + y
let addAsync = x => async y => x + y
add()
為 sync function,而 addAsync()
為 async function,
第 6 行
let f = pipe(
addAsync(2),
andThen(add(3)),
andThen(addAsync(4))
)
若想同時 pipeline sync function 與 async function,則第二個 function 之後都要加上 andThen()
。
pipeP()
import { pipeP, add } from 'ramda'
import { async2 } from 'wink-fp'
let addAsync = async2(add)
let f = pipeP(
addAsync(2),
add(3),
addAsync(4)
)
f(1) // ?
也因為一堆 function 都要加上 andThen()
,Ramda 提出了 pipeP()
,專門應付 sync function 與 async function 的組合。
pipeP()
((a → Promise b), (b → Promise c), …, (y → Promise z)) → (a → Promise z)
若第一個 function 為 async function,可使用pipeP()
組合後續 sync function 或 async function
pipeWith() + andThen()
import { pipeWith, andThen, unapply, add } from 'ramda'
import { async2 } from 'wink-fp'
let addAsync = async2(add)
let pipeP = unapply(pipeWith(andThen))
let f = pipeP(
addAsync(2),
add(3),
addAsync(4)
)
f(1) // ?
pipeP()
在實務上很好用,只可惜在 0.26.0
被列為 deprecated
,因為被 pipeWith()
所取代。
pipeWith()
雖然更萬用,但必須將 function 以 array 傳入,與傳統 pipe()
習慣不同。
我們可將 pipeWith()
與 andThen()
組合,最後以 unapply()
轉成 variadic function。
Conclusion
pipeWith()
實務上很少直接拿來使用,但用來產生其他pipeX()
系列 higher order function 則非常好用
Reference
Ramda, pipeWith()
Ramda, isNil()
Ramda, unapply()
Ramda, pipe()
Ramda, andThen()
Ramda, pipeP()
Ramda, add()