ap
才是正統 S Combinator,但 chain
的 Type Signature 與 ap
非常類似,姑且稱之為 S’ Combinator。
Version
Ramda 0.27.1
Imperative
let data = [1, 2, 3]
let f = x => (x.push (x [0]), x)
f (data) // ?
若我們希望將傳入 Array 的第一個 element 加到原 Array 最後,Imperative 會使用 x [0]
取出 element,並使用 Array.prototype.push
新增 element。
因為 push
是回傳 Array Length,因此特別使用 ()
回傳 Array。
Functional
import { append, head } from 'ramda'
let data = [1, 2, 3]
let f = x => append (head (x)) (x)
f (data) // ?
Functional 會使用 head
取得 Array 的第一個 element,然後在 append
到最後並回傳。
converge
import { converge, append, head, identity as I } from 'ramda'
let data = [1, 2, 3]
converge (append) ([head, I]) (data) // ?
一般為了 Point-free,都會朝向 pipe
、compose
、useWith
或 converge
這些 function 著手。
因為發現 append
是實質最後一個 function,第一個 argument 經過 head
,第二個不變,很直覺會朝向 converge
重構。
當出現
converge (f) ([g, I])
時,它等效於chain (f) (g)
chain
import { chain, append, head } from 'ramda'
let data = [1, 2, 3]
chain (append) (head) (data) // ?
這種第一個 argument 須經過運算後再傳入的 pattern,很類似 S Combinator,姑且稱之為 S’ combinator,可使用 chain
組合使其 Point-free。
chain (f) (g) (x) = f (g (x)) (x)
而 chain
正是 S’ Combinator 的實現。
chain
(a → b → c) → (b → a) → b → c
實現 S_ Combinator
a -> b -> c
:傳入 binary function
b -> a
:傳入 unary function
b
:為 data
c
:回傳結果
ap
ap (f) (g) (x) = f (x) (g (x))
眼尖的人應該會發現 ap
與 chain
非常接近:
ap (f) (g) (x) = f (x) (g (x))
chain (f) (g) (x) = f (g (x)) (x)
ap
在第二個 argument 先經過其他 function 運算,而 chain
則在第一個 argument 先經過其他 function 運算。
Conclusion
- Binary function 若只有一個 argument 經過 function 運算,可使用
ap
或chain
組合 - Binary function 若兩個 argument 都需經過 function 運算,可使用
converge
組合 - 若一開始看不出使用
ap
,可先重構成converge
,當發現converge (f) ([g, I])
時再重構成chain