Simple Explanation of Applicative Functor

實際簡白的 Applicative Functor 解釋

前言

從先前 Functor 的概念出發,透過把數值包裹在容器促使函式組合如下:

const addThree = (x) => x + 3
const multiplyByTwo = (x) => x * 2
Maybe(2)
.map(addThree); // Maybe(5)
.map(multiplyByTwo); // Maybe(10)

但如果函數本身也被包在容器中呢?

const maybeAddThree = Maybe(addThree)

Functor 的 map 只能作用在數值上,而不能是「存在於容器中的數值」上,所以才需要 Applicative Functor。

什麼是 Applicative Functor?

一種比 Functor 更多功能的結構,除了滿足 Functor 的功能外還能「讓盒子裡的函數作用(apply)到另一盒子的值」。

function Maybe(value) {
return value == null ? Nothing() : Just(value);
}
function Just(value) {
return {
map: f => Maybe(f(value)),
apply: f => f.map(value),
getOrElse: () => value,
};
}
function Nothing() {
return {
map: () => Nothing(),
apply: () => Nothing(),
getOrElse: defaultValue => defaultValue,
};
}
const add = a => b => a + b;
Maybe(add)
.apply(Maybe(1)) // Maybe(1 + b)
.apply(Maybe(1)) // Maybe(1 + 1)
.getOrElse(0) // 2

以上 Applicative Functor 達成在不拆解包裝的情況下進行運算。

我還是不懂 Applicative Functor 的應用場合

假設要驗證使用者「輸入名稱」與「Email」且轉小寫與大寫,每個驗證都可能失敗,使用 Functor 會寫成這樣:

const validateName = name =>
name ? Just(name) : Nothing();
const validateEmail = email =>
email.includes('@') ? Just(email) : Nothing();
const nameResult = validateName("webdong")
.map(name => name.toUpperCase())
.getOrElse("Invalid name")
const emailResult = validateEmail("[email protected]")
.map(email => email.toLowerCase())
.getOrElse("Invalid email")
const makeUser = name => email => ({ name, email });
console.log(makeUser(nameResult)(emailResult))

多個 Functor 處理起來會是個問題,或是在 Functor 中處理其他 Functor 也是個問題:

// Just(Just({ name, email }))
validateName("Rice")
.map(name =>
validateEmail("[email protected]")
.map(email => makeUser(name)(email))
);

有了 Applicative Functor 就可以輕鬆組合多個 Functor:

const validateName = name =>
name ? Maybe(name) : Nothing();
const validateEmail = email =>
email.includes('@') ? Maybe(email) : Nothing();
const makeUser = name => email => ({ name, email });
Maybe(makeUser)
.apply(validateName("webdong"))
.apply(validateEmail("[email protected]"))
// → Maybe({ name: "Rice", email: "[email protected]" })
Maybe(makeUser)
.apply(validateName(""))
.apply(validateEmail("[email protected]"))
// → Nothing

或是將 Promise 視為 Applicative Functor 的視角來看:

Promise.resolve(add)
.then(f => Promise.all([Promise.resolve(2), Promise.resolve(3)])
.then(([a, b]) => f(a)(b)))
.then(console.log); // 5

Applicative Functor 定義

Applicative Functor 可以被視為 Functor 的進階版。 Functor 只能「讓函數作用在容器裡的值上」, 而 Applicative 則進一步允許「容器裡的函數」作用在「另一個容器裡的值」上,要實現 Applicative 需要實踐兩種方法:

  • of:把一般值包裝進容器。
  • applyap:讓「裝在容器裡的函數」作用到「裝在容器裡的值」。
  1. Identity
// 用一個「不改變內容」的函數包起來再套用,結果應與原值相同。
// 例如:Maybe.of(x => x).apply(Maybe(5)) === Maybe(5)
A.of(x => x).apply(v) === v
  1. Homomorphism
// 直接應用函數與把函數和值都包起來後再套用,結果應一致。
// 例如:Maybe.of(f).apply(Maybe.of(x)) === Maybe.of(f(x))
A.of(f).apply(A.of(x)) === A.of(f(x))
  1. Interchange
// 套用的順序可互換,只要函數應用邏輯相同。
// 例如:Maybe(fn).apply(Maybe.of(y)) === Maybe.of(f => f(y)).apply(Maybe(fn))
u.apply(A.of(y)) === A.of(f => f(y)).apply(u)
  1. Composition
// 套用多個函數時,應與它們的合成結果一致。
// 例如: Maybe.of(compose).apply(u).apply(v).apply(w)
// 等同於:u.apply(v.apply(w))
A.of(compose).apply(u).apply(v).apply(w) === u.apply(v.apply(w))