使用 Function Pipeline 時,有時會想使用前兩個 Function 的回傳值,但因為橫跨在不同 Function 彼此看不到對方,此時可使用 Pair 將前一個 Function 回傳值包起來傳到下一個 Function。
Version
macOS Catalina 10.15.4
VS Code 1.44.2
Quokka 1.0.289
Ramda 0.27.0
Dependent Function Call
import { pipe, prop, find, flip, propEq } from 'ramda'
let categories = [
{ id: 1, value: 'FP' },
{ id: 2, value: 'FRP' },
{ id: 3, value: 'JS' }
]
let fetchCategory = pipe(
propEq('id'),
flip(find)(categories)
)
let fetchBook = _ =>
({ title: 'FP in JavaScript',
price: 100,
categoryId: 1 })
let f = pipe(
fetchBook,
prop('categoryId'),
fetchCategory,
prop('value')
)
f() // ?
14 行
let fetchBook = _ =>
({ title: 'FP in JavaScript',
price: 100,
categoryId: 1 })
fetchBook()
只回傳 categoryId
。
第 9 行
let fetchCategory = pipe(
propEq('id'),
flip(find)(categories)
)
若要取得 category
,還要呼料 fetchCategory()
。
19 行
let f = pipe(
fetchBook,
prop('categoryId'),
fetchCategory,
prop('value')
)
若最後只想取得 category()
,可使用 function pipeline 組合 fetchBook()
與 fetchCategory()
。
Return Array
import { pipe, prop, find, propEq, flip } from 'ramda'
let categories = [
{ id: 1, value: 'FP' },
{ id: 2, value: 'FRP' },
{ id: 3, value: 'JS' }
]
let fetchCategory = pipe(
propEq('id'),
flip(find)(categories)
)
let fetchBook = _ =>
({ title: 'FP in JavaScript',
price: 100,
categoryId: 1 })
let f = pipe(
fetchBook,
x => [fetchCategory(x.categoryId), x],
([x, y]) => [y.title, x.value]
)
f() // ?
若需求改變,我們不只希望回傳 category
而已,而是同時回傳 title
與 category
。
21 行
x => [fetchCategory(x.categoryId), x],
title
來自於 fetchBook()
,而 category
來自於 fetchCategory()
,這對 function pipeline 就比較尷尬,因為橫跨在不同 function 內,彼此看不到對方。
實務上會使用 []
將前一個 function 回傳值包起來傳到下一個 function,讓下一個 function 也能讀取得到。
22 行
([x, y]) => [y.title, x.value]
由於前一個 function 包成 array 回傳,因此使用 ES6 的 array destructuring 解開,再整理成我們要的格式回傳。
Return Pair
import { pipe, prop, find, chain, flip, identity } from 'ramda'
import { propEq, converge, pair, nth, } from 'ramda'
let categories = [
{ id: 1, value: 'FP' },
{ id: 2, value: 'FRP' },
{ id: 3, value: 'JS' }
]
let fetchCategory = pipe(
propEq('id'),
flip(find)(categories)
)
let fetchBook = _ =>
({ title: 'FP in JavaScript',
price: 100,
categoryId: 1 })
let f = pipe(
fetchBook,
converge(pair, [
pipe(prop('categoryId'), fetchCategory),
identity
]),
converge(pair, [
pipe(nth(1), prop('title')),
pipe(nth(0), prop('value'))
])
)
f() // ?
22 行
converge(pair, [
pipe(prop('categoryId'), fetchCategory),
identity
]),
回傳兩個 element 的 array 其實就是 pair,可透過 converge()
使其 point-free。
26 行
converge(pair, [
pipe(nth(1), prop('title')),
pipe(nth(0), prop('value'))
])
最後回傳也是 pair,亦可再次使用 converge()
使其 point-free。
chain()
import { pipe, prop, find, chain, flip } from 'ramda'
import { propEq, converge, pair, nth, } from 'ramda'
let categories = [
{ id: 1, value: 'FP' },
{ id: 2, value: 'FRP' },
{ id: 3, value: 'JS' }
]
let fetchCategory = pipe(
propEq('id'),
flip(find)(categories)
)
let fetchBook = _ =>
({ title: 'FP in JavaScript',
price: 100,
categoryId: 1 })
let f = pipe(
fetchBook,
chain(pair, pipe(prop('categoryId'), fetchCategory)),
converge(pair, [
pipe(nth(1), prop('title')),
pipe(nth(0), prop('value'))
])
)
f() // ?
22 行
chain(pair, pipe(prop('categoryId'), fetchCategory)),
當出現 converge(f, [g, identity])
的 pattern 時,可重構成 chain(f, g)
。
Extract Function
import { pipe, prop, find, chain, flip } from 'ramda'
import { propEq, converge, pair, nth } from 'ramda'
let categories = [
{ id: 1, value: 'FP' },
{ id: 2, value: 'FRP' },
{ id: 3, value: 'JS' }
]
let fetchCategory = pipe(
propEq('id'),
flip(find)(categories)
)
let fetchBook = _ =>
({ title: 'FP in JavaScript',
price: 100,
categoryId: 1 })
let getTitle = pipe(
nth(1),
prop('title')
)
let getValue = pipe(
nth(0),
prop('value')
)
let getCategory = pipe(
prop('categoryId'),
fetchCategory
)
let f = pipe(
fetchBook,
chain(pair, getCategory),
converge(pair, [getTitle, getValue])
)
f() // ?
35 行
let f = pipe(
fetchBook,
chain(pair, getCategory),
converge(pair, [getTitle, getValue])
)
將 pipe()
部分抽成獨立 function 後,f()
的可讀性更高了。
Conclusion
- 以 array 或 pair 將上一個 function 的值包起來傳到下一個 function 的靈感來自於
Promise.all()
,它會將 synchronous 與 asynchronous 值包起來傳到下一個then()
,此技巧也能用於 sync function
Reference
Ramda, pipe()
Ramda, prop()
Ramda, find()
Ramda, chain()
Ramda, flip()
Ramda, propEq()
Ramda, converge()
Ramda, pair()
Ramda, nth()