點燈坊

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

常用 Function Composition 方式總結

Sam Xiao's Avatar 2020-05-02

一般談到 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。

usewith000

Functional

import { add, multiply } from 'ramda'

let f = x => multiply(2, add(1, x))

f(2) // ?

若使用 FP,就不會使用 operator 了,全部改用 function。

usewith001

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。

usewith002

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。

usewith003

map()

import { add, multiply, map } from 'ramda';

let fn = map(
  multiply(2),
  add(1)
);

fn(2); // ?

若只 compose() 兩個 function,可等價使用 map() 替換,因為 function 也是 Functor,組合 function 相當於 function 加以 map()

useWith005

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。

usewith002

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()

usewith006

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()

compose008

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()