一般談到 Function Composition,直覺都會想到 pipe()
與 compose()
,事實上就廣義而言, map()
、useWith()
、 converge()
與 chain()
也算是。
Version
macOS Catalina 10.15.4
VS Code 1.44.2
Quokka 1.0.289
Ramda 0.27.0
Imperative
let f = x => (x + 1) * 2
f(2) // ?
先 + 1
再 * 2
,若使用 imperative,就會使用 operator。
Functional
import { add, multiply } from 'ramda'
let f = x => multiply(2, add(1, x))
f(2) // ?
若使用 FP,就不會使用 operator 了,全部改用 function。
pipe()
import { add, multiply, pipe } from 'ramda'
let f = pipe(
add(1),
multiply(2)
)
f(2) // ?
以 dataflow 角度,arg
是先執行 add(1)
,再將結果傳給 multiply(2)
執行,因此可用 pipe()
加以組合,還順便 point-free。
compose()
import { add, multiply, compose } from 'ramda'
let f = compose(
multiply(2),
add(1)
)
f(2) // ?
由另外一個角度思考:fn()
其實是 add(1)
與 multiply(2)
兩個 funciton 組合而成,因此可使用 compose()
加以組合,還順便 point-free。
map()
import { add, multiply, map } from 'ramda';
let fn = map(
multiply(2),
add(1)
);
fn(2); // ?
若只 compose()
兩個 function,可等價使用 map()
替換,因為 function 也是 Functor
,組合 function 相當於 function 加以 map()
。
useWith()
import { add, multiply, useWith } from 'ramda';
let f = useWith(
multiply(2), [add(1)]
)
f(2) // ?
再由另外一個叫度去思考:multiply(2)
才是最終要執行的 main function,add(1)
只是處理 argument 的 transformer function,因此可使用 useWith()
加以組合,還順便 point-free。
converge()
import { add, multiply, converge } from 'ramda'
let f = converge(
multiply(2), [add(1)]
)
f(2) // ?
converge()
與 useWith()
類似,唯 useWith()
是 array 有幾個 element,argument 就有幾個;而 converge()
是無論 array 有幾個 element,argument 都只有一個套用 array 所有 function。
因為此例 argument 都只有一個,使用 converge()
等效於 useWith()
。
chain()
import { pipe, map, flatten, inc } from 'ramda'
let data = [[1, 2], [3, 4], [5, 6]]
let f = pipe(
map(map(inc)),
flatten
)
f(data) // ?
若想將 nested array 的 element 都加 1
,然後 flatten 為一層 array,直覺會使用兩層 map()
與 flatten()
組合。
import { pipe, map, flatten, inc, chain } from 'ramda'
let data = [[1, 2], [3, 4], [5, 6]]
let f = chain(map(inc))
f(data) // ?
pipe(map(f), flatten)
可等效重構成 chain(f)
。
import { append, head } from 'ramda'
let data = [1, 2, 3]
let f = a => append(head(a), a)
f(data) // ?
若想使用 append()
對 array 新增 element,且其值由 head()
取得。
import { append, head, converge, identity } from 'ramda'
let data = [1, 2, 3]
let f = converge(append, [head, identity])
f(data) // ?
若想對 f()
point-free,直覺會使用 converge()
組合 append()
、head()
與 identity()
。
import { append, head, chain } from 'ramda'
let data = [1, 2, 3]
let f = chain(append, head)
f(data) // ?
converge(f, [t, identity])
可等效重構成 chain(f, t)
。
chain(f, t)(x) = f(t(x), x),當 f() 與 t() 都需要 data,且 f() 的第一個 argument 必須先經過 t() 時,也適用於 chain()
Conclusion
- FP 與數學一樣,本來就不是單一方法解題,所以存在不同 function 組合方式
useWith()
一般都用在多 argument,若退化成單一 argument,等效於pipe()
、compose()
與converge()
chain()
屬於較進階的組合,可用於flatMap()
,或者等效於converge(f, [t, identity])
Peference
Ramda, pipe()
Ramda, compose()
Ramda, map()
Ramda, useWith()
Ramda, add()
Ramda, multiply()
Ramda, converge()
Ramda, chain()