Simple Explanation of Functor

實際簡白的 Functor 解釋

前言

我不是數學家也對於範疇論🔗 沒有太大興趣,但它深刻的與 Functional Programming 進階理念掛勾,透過實戰應用了解開發層面這些理論可以如何撰寫更好維護的程式

Functor

想像成一個「裝值的盒子」、「遵守特定規則的可映射結構」允許對內容進行運算,但同時又保留了盒子的結構,舉例: 2 + 3 = 5 但如果數字被「包裝」起來呢?例如:

  • [2] (在陣列裡)
  • Promise.resolve(2) (在非同步計算裡)
  • Maybe(2) (可能有值,也可能沒值)

這時候不能直接對 2 進行改動,但 Functor 提供了方式在容器裡操作值通常被稱作 map

  • [2].map(x => x + 3); // [5]
  • Promise.resolve(2).then(x => x + 3); // Promise(5)
  • Maybe(2).map(x => x + 3); // Maybe(5)

為什麼需要 Functor?

把數值包入「盒子」是為了讓 數值存在上下文,舉例製作一個 Maybe Functor 代表「可能存在、也可能不存在的值」,透過「盒子」內定義的規則根據輸入返回不同「盒子」。

  • 如果有值,就正常運算。
  • 如果無值,就跳過運算返回 Nothing
function Maybe(value) {
return value == null ? Nothing() : Just(value);
}
function Just(value) {
return {
map: fn => Just(fn(value)),
getOrElse: () => value,
};
}
function Nothing() {
return {
map: () => Nothing(),
getOrElse: defaultValue => defaultValue,
};
}
// 相較於失敗拿到 Nothing,使用 `getOrElse` 返回數值或提供預設值
const a = Maybe(2).map(x => x + 3).getOrElse(0);
const b = Maybe(null).map(x => x + 3).getOrElse(0);
console.log(a, b) // 5, 0

以上 Maybe Functor 的例子透過替數值包裹上下文避免了需要重複撰寫無值的條件判斷:

// 對數值不斷檢查
function getUserEmail(userId) {
const user = findUser(userId);
if (user !== null && user !== undefined) {
const profile = user.profile;
if (profile !== null && profile !== undefined) {
const email = profile.email;
if (email !== null && email !== undefined) {
return email.toLowerCase();
}
}
}
}
// 數值包裹於 Functor 上下文中
function getUserEmail(userId) {
return Maybe(findUser(userId))
.map(user => user.profile)
.map(profile => profile.email)
.map(email => email.toLowerCase())
.getOrElse('[email protected]');
}

Functor 定義

  • Identity

    // 不改變結構的情況下改變其內容
    // 舉例:[1, 2, 3].map(x => x) 等同 [1, 2, 3]
    Functor.map(x => x) === Functor
  • Composition

    // 連續 map 兩個函數,應該等同於 map 它們的合成函數
    // 舉例:[1, 2, 3].map(x => f(g(x))) 等同 [1, 2, 3].map(g).map(f)
    const f = (x) => x * 2
    const g = (x) => x + 1
    Functor.map(x => f(g(x))) === Functor.map(g).map(f)

總結

「盒子」的概念在程式語言中正式來說是:「型別建構子」,用於接收一個型別作為參數,生成一個新型別
type Maybe<T> = Just<T> | Nothing
  • Functor 是一種具有 map 方法的結構,允許對其中的值進行轉換同時保持結構不變。
  • Functor 讓函數可以基於容器進行組合,而不必關心值實際位於哪個上下文中,意味著可以被用於封裝副作用或情境。

延伸閱讀