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 让函数可以基于容器进行组合,而不必关心值实际位于哪个上下文中,意味着可以被用于封装副作用或情境。

延伸阅读