JotaiJotai

Jotai
React 原始而灵活的状态管理

函数式编程和Jotai

意想不到的相似之处

如果你仔细观察getter函数,你可能会发现它与某个JavaScript语言特性有着惊人的相似之处。

const nameAtom = atom('访客')
const countAtom = atom(1)
const greetingAtom = atom((get) => {
const name = get(nameAtom)
const count = get(countAtom)
return (
<div>
你好,{name}!你已经访问过这个页面 {count} 次了。
</div>
)
})

现在,将这段代码与 asyncawait 进行比较:

const namePromise = Promise.resolve('访客')
const countPromise = Promise.resolve(1)
const greetingPromise = (async function () {
const name = await namePromise
const count = await countPromise
return (
<div>
你好,{name}!你已经访问过这个页面 {count} 次了。
</div>
)
})()

这种相似性并非巧合。atom和promise都是单子(Monads),这是一个来自函数式编程的概念。两个例子中使用的语法被称为do-notation,它是更简洁的单子接口的语法糖。


单子接口负责实现atom和promise接口的流畅性。单子接口允许我们用nameAtomcountAtom来定义greetingAtom,同样也允许我们用namePromisecountPromise来定义greetingPromise

对于那些对数学感兴趣的人来说,如果你能为一个结构(如AtomPromise)实现以下函数,那么这个结构就是一个单子。一个有趣的练习是尝试为Atoms、Promises和Arrays实现ofmapjoin

type SomeMonad<T> = /* ... */
declare function of<T>(plainValue: T): SomeMonad<T>
declare function map<T, V>(
anInstance: SomeMonad<T>,
transformContents: (contents: T) => V
): SomeMonad<V>
declare function join<T>(nestedInstances: SomeMonad<SomeMonad<T>>): SomeMonad<T>

单子已经吸引了数学家60年,程序员40年。有无数关于单子模式的资源,比如Traversablesequence。去看看它们吧!

深入理解 Monads(单子)

Monads 是函数式编程中的一个核心概念,它提供了一种强大的方式来抽象和管理复杂的计算和副作用。以下是对 Monads 的更详细解释:

  1. 定义:Monad 是一种设计模式,用于封装计算和管理副作用。它提供了一种统一的方式来处理各种复杂的编程模式。

  2. 主要特性

    • 封装:Monads 封装了值和相关的计算。
    • 组合:Monads 允许你以一种清晰、可组合的方式链接多个操作。
    • 上下文管理:Monads 可以维护计算的上下文,如异步状态、错误处理等。
  3. 在 JavaScript/TypeScript 中的应用

    • Promise 是 Monad 的一个典型例子,它封装了异步操作。
    • 状态管理库(如 Jotai 中的 Atom)也展现了 Monad 的特性。
  4. 核心操作

    • of(或 return):将普通值包装到 Monad 中。
    • map(或 fmap):转换 Monad 中的值。
    • join(常与 flatMapbind 结合):处理嵌套的 Monad 结构。
  5. 优势

    • 提供了处理复杂操作序列的统一方法。
    • 使代码更加清晰、可组合和易于管理。
    • 有助于分离关注点,特别是在处理副作用时。
  6. 常见的 Monad 类型

    • Maybe Monad:处理可能不存在的值。
    • Either Monad:处理可能成功或失败的操作。
    • Reader Monad:处理共享环境或配置。
    • State Monad:处理可变状态。
  7. 在实践中的应用

    • 错误处理:使用 MaybeEither 来优雅地处理错误。
    • 异步编程:使用 Promise 或类似的结构来管理异步操作。
    • 状态管理:在前端框架中使用类 Monad 的结构来管理应用状态。

理解 Monads 可能需要一些时间,因为它是一个相对抽象的概念。然而,一旦掌握,它就成为处理复杂编程问题的强大工具。在函数式编程中,Monads 是处理副作用和复杂操作序列的关键工具。

通过学习和应用 Monads,开发者可以编写更加健壮、可维护和可扩展的代码。这种编程范式在处理复杂的异步操作、状态管理和错误处理时特别有用。

JavaScript 中的 Monads 实例

为了更好地理解 Monads,让我们来看一些 JavaScript 中的具体例子:

  1. Maybe Monad

Maybe Monad 用于处理可能存在或不存在的值,避免了空值检查。

class Maybe {
constructor(value) {
this.value = value;
}
static of(value) {
return new Maybe(value);
}
isNothing() {
return this.value === null || this.value === undefined;
}
map(fn) {
return this.isNothing() ? Maybe.of(null) : Maybe.of(fn(this.value));
}
}
// 使用示例
const result = Maybe.of(5)
.map(x => x * 2)
.map(x => x + 1)
.map(x => x / 0)
.map(x => x + 10);
console.log(result.value); // 输出: null,因为除以 0 导致了 NaN

在这个例子中,Maybe Monad 允许我们链式调用多个操作,即使中间出现了无效值(如 NaN),整个链还是能安全地执行完毕。这展示了 Monads 如何优雅地处理错误情况。

  1. Promise Monad

Promise 是 JavaScript 中处理异步操作的 Monad。

// 模拟异步操作
const fetchUser = id => Promise.resolve({ id, name: 'John Doe' });
const fetchPosts = userId => Promise.resolve([
{ id: 1, title: 'Hello World' },
{ id: 2, title: 'About Monads' }
]);
// 使用 Promise Monad
fetchUser(1)
.then(user => fetchPosts(user.id))
.then(posts => console.log(posts))
.catch(error => console.error(error));

Promise 允许我们以一种清晰的方式链接多个异步操作,处理成功和失败的情况。这个例子展示了 Monads 如何简化异步编程。

  1. 自定义 State Monad

State Monad 用于处理可变状态,而不直接修改外部状态。

class State {
constructor(runState) {
this.runState = runState;
}
static of(value) {
return new State(state => [value, state]);
}
map(fn) {
return new State(state => {
const [a, newState] = this.runState(state);
return [fn(a), newState];
});
}
chain(fn) {
return new State(state => {
const [a, newState] = this.runState(state);
return fn(a).runState(newState);
});
}
}
// 使用示例
const addToCount = amount => new State(count => [amount, count + amount]);
const computation = State.of(0)
.chain(addToCount(5))
.chain(addToCount(10))
.map(total => `Total: ${total}`);
const [result, finalState] = computation.runState(0);
console.log(result); // 输出: "Total: 15"
console.log(finalState); // 输出: 15

在这个例子中,State Monad 允许我们以一种可组合的方式处理状态变化,而不需要显式地传递状态。这展示了 Monads 如何帮助管理复杂的状态转换。

这些例子展示了 Monads 如何在 JavaScript 中实现和使用。它们提供了一种统一的方式来处理值的转换、副作用和复杂的控制流。Monads 的核心思想是将值包装在一个上下文中(如 Maybe、Promise 或 State),然后提供操作这个上下文的方法(如 map、chain 或 then)。这允许我们以一种可组合和可管理的方式处理复杂的操作序列。

通过这些具体的例子,我们可以看到 Monads 如何在实际编程中应用,以及它们如何帮助我们编写更清晰、更易于维护的代码。无论是处理可能不存在的值、管理异步操作,还是处理复杂的状态变化,Monads 都提供了优雅的解决方案。