diff --git a/docs/basics/functional-programming-and-jotai.mdx b/docs/basics/functional-programming-and-jotai.mdx index 24aa4cb4c3..c259d04c9b 100644 --- a/docs/basics/functional-programming-and-jotai.mdx +++ b/docs/basics/functional-programming-and-jotai.mdx @@ -22,7 +22,7 @@ const greetingAtom = atom((get) => { }) ``` -Now, compare that code with `async`–`await`: +Compare that code with `async`–`await`: ```tsx const namePromise = Promise.resolve('Visitor') @@ -38,31 +38,97 @@ const greetingPromise = (async function () { })() ``` -This similarity is no coincidence. Both atoms and promises are **Monads**, a -concept from **functional programming**. The syntax used in both examples is -called **do-notation**, a syntax sugar for the plainer monad interface. +This similarity is no coincidence. Both atoms and promises are **Monads**†, a +concept from functional programming. The syntax used in both `greetingAtom` and +`greetingPromise` is known as **do-notation**, a syntax sugar for the plainer +monad interface. ---- +### About monads The monad interface is responsible for the fluidity of the atom and promise interfaces. The monad interface allowed us to define `greetingAtom` in terms of `nameAtom` and `countAtom`, and allowed us to define `greetingPromise` in terms of `namePromise` and `countPromise`. -For the mathematically inclined, a structure (like `Atom` or `Promise`) is a -monad if you can implement the following functions for it. A fun exercise is -trying to implement `of`, `map` and `join` for Atoms, Promises and Arrays. +If you're curious, a structure (like `Atom` or `Promise`) is a monad if you can +implement the following functions for it. A fun exercise is trying to implement +`of`, `map` and `join` for Arrays. -```typescript -type SomeMonad = /* ... */ +```ts +type SomeMonad = /* for example... */ Array declare function of(plainValue: T): SomeMonad declare function map( anInstance: SomeMonad, - transformContents: (contents: T) => V + transformContents: (contents: T) => V, ): SomeMonad declare function join(nestedInstances: SomeMonad>): SomeMonad ``` +The shared heritage of Promises and Atoms means many patterns and best-practices +can be reused between them. Let's take a look at one. + +### Sequencing + +When talking about callback hell, we often mention the boilerplate, the +indentation and the easy-to-miss mistakes. However, plumbing a single async +operation into another single async operation was not the end of the callback +struggle. What if we made four network calls and needed to wait for them all? +A snippet like this was common: + +```ts +const nPending = 4 +const results: string[] +function callback(err, data) { + if (err) throw err + results.push(data) + if (results.length === nPending) { + // do something with results... + } +} +``` + +But what if the results have different types? and the order was important? Well, +we'd have a lot more frustrating work to do! This logic would be duplicated at +each usage, and would be easy to mess up. Since ES6, we simply call `Promise.all`: + +```ts +declare function promiseAll(promises: Array>): Promise> +``` + +`Promise.all` "rearranges" `Array` and `Promise`. It turns out this concept, +_sequencing_, can be implemented for all monad–_Traversable_ pairs. Many kinds +of collections are Traversables, including Arrays. For example, this is a case +of sequencing specialized for atoms and arrays: + +```ts +function sequenceAtomArray(atoms: Array>): Atom> { + return atom((get) => atoms.map(get)) +} +``` + +### Culmination + Monads have been an interest to mathematicians for 60 years, and to programmers -for 40. There are countless resources out there on patterns for monads, such -as `Traversable`–`sequence`. Take a look at them! +for 40. There are many resources out there on patterns for monads. Take a look +at them! Here are a select few: + +- [_Inventing Monads_](https://stopa.io/post/247) by Stepan Parunashvili +- [_How Monads Solve Problems_](https://thatsnomoon.dev/posts/ts-monads/) by ThatsNoMoon +- Wiki page [list of monad tutorials](https://wiki.haskell.org/Monad_tutorials_timeline) +- [Typeclassopedia](https://wiki.haskell.org/Typeclassopedia) (for the curious) + +Learning a neat trick on using promises may well translate to atoms, as +`Promise.all` and `sequenceAtomArray` did. Monads are not magic, just unusually +useful, and a tool worth knowing. + +--- + +_Notes_ + +**[†]** The ES6 Promise is not a completely valid monad because it cannot nest other +Promises, e.g. `Promise>` is semantically equivalent to +`Promise`. This is why Promises only have a `.then`, and not both a +`.map` and `.flatMap`. ES6 Promises are probably more properly described as +"monadic" rather than as monads. + +Unlike ES6 Promises, the ES6 Array is a completely lawful monad.