iT邦幫忙

2021 iThome 鐵人賽

DAY 16
0
Software Development

Functional Programming For Everyone系列 第 16

Day 16 - Apply

到目前為止我們已經知道了 Functor 可以將 effect 跟 pure function 進行 compose,但我們沒有提到一點是 Functor 無法應付的,而今天要介紹的主角就是用來解決此一情境的,Apply

f g composition
pure pure compose(f, g)
effects pure(unary) f.map(g)
effects pure(n-ary) ?

舉例來說,現在有兩個 Identity,分別是 Identity(1)Identity(2),我們要如何將其進行相加

如果用一般正常函式的相加,看來是行不通

const add = (x, y) => x + y;

add(Identity.of(1), Identity.of(2)) // [object Object][object Object]

可能有讀者想到, 如果用 map 將分別將 Identity 內的值取出,再將 add 在套用到取出後的值就好了

const result = Identity.of(1).map(x => Identity.of(2).map(y => add(x, y))) // Identity(Identity(3))

確實,這樣也是一個辦法,但這種方法不但會導致前後相依,也就是必須等待前面的運算完成後才能進行下一個運算,也會使運算後變成深層結構 Identity(Identity(3)),取值時必須

result.val.val

那該怎麼辦呢? 有沒有方法讓其既可以是獨立的也可以維持單層結構,就讓來介紹 Apply 吧!

Apply

Type Signature

ap :: Apply f => f a ~> f (a -> b) -> f b

Law

  • Composition: v.ap(u.ap(a.map(f => g => x => f(g(x))))) === v.ap(u).ap(a)

Implementation

所以要解決上述提到的問題可以分成四步驟

1. 將 add 函式進行 currying

const add = R.curry((x, y) => x + y);

2. 實作 ap

const Identity = (val) => ({
  val,
  map: (f) => {
    const result = f(val);
    return Identity(result); 
  },
  ap: function (o) {
    // 根據 Fantasy-land 對 Apply 定義,其為 `a.ap(b)` 其 `b` 一定會是函式,而在此範例則是 o.val 必為函式
    return this.map(o.val);
  },
  inspect: () => `Identity(${val})`,
});

3. 建立一個 helper function lift2

const lift2 = g => f1 => f2 => f2.ap(f1.map(g));

注意: 這就為什麼我們的 g 一定要進行 currying

而其實 ap 就是將兩個 Container 進行合併,相較於前面章節提過的 Semigroup 則是透過呼叫 concat進行合併。

另外如果今天是要將 g (3-ary) 合併三個 Container

const lift3 = g => f1 => f2 => f3 => f3.ap(f2.ap(f1.map(g)));

4. 接下來就可以輕鬆的解決一開始的問題

lift2(add)(Identity(1))(Identity(2)) // Identity(3)

小結

沒錯,我們現在解決了 effects 跟 pure(n-ary) 的問題!!! 所以來更新一下一開始提到的表吧!

f g composition
pure pure compose(f, g)
effects pure(unary) f.map(g)
effects pure(n-ary) f1.map(g).ap(f2)

感謝大家的閱讀!!

Reference

  1. Apply

上一篇
Day 15 - Contravariant Functor
下一篇
Day 17 - Applicative
系列文
Functional Programming For Everyone30

尚未有邦友留言

立即登入留言