點燈坊

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

使用 chain 實現 S' Combinator

Sam Xiao's Avatar 2021-07-10

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。

chain003

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 到最後並回傳。

chain000

converge

import { converge, append, head, identity as I } from 'ramda'

let data = [1, 2, 3]

converge (append) ([head, I]) (data) // ?

一般為了 Point-free,都會朝向 pipecomposeuseWithconverge 這些 function 著手。

因為發現 append 是實質最後一個 function,第一個 argument 經過 head,第二個不變,很直覺會朝向 converge 重構。

當出現 converge (f) ([g, I]) 時,它等效於 chain (f) (g)

chain001

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:回傳結果

chain002

ap

ap (f) (g) (x) = f (x) (g (x))

眼尖的人應該會發現 apchain 非常接近:

  • 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 運算,可使用 apchain 組合
  • Binary function 若兩個 argument 都需經過 function 運算,可使用 converge 組合
  • 若一開始看不出使用 ap,可先重構成 converge,當發現 converge (f) ([g, I]) 時再重構成 chain

Reference

Ramda, chain