Skip to content

Commit

Permalink
docs(functional): cover sequence (#2729)
Browse files Browse the repository at this point in the history
* docs(functional): cover sequence

* docs(functional): add notes section
  • Loading branch information
probeiuscorp authored Sep 11, 2024
1 parent 74b3803 commit 9b83585
Showing 1 changed file with 79 additions and 13 deletions.
92 changes: 79 additions & 13 deletions docs/basics/functional-programming-and-jotai.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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<T> = /* ... */
```ts
type SomeMonad<T> = /* for example... */ Array<T>
declare function of<T>(plainValue: T): SomeMonad<T>
declare function map<T, V>(
anInstance: SomeMonad<T>,
transformContents: (contents: T) => V
transformContents: (contents: T) => V,
): SomeMonad<V>
declare function join<T>(nestedInstances: SomeMonad<SomeMonad<T>>): SomeMonad<T>
```

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<T>(promises: Array<Promise<T>>): Promise<Array<T>>
```

`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<T>(atoms: Array<Atom<T>>): Atom<Array<T>> {
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<Promise<number>>` is semantically equivalent to
`Promise<number>`. 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.

0 comments on commit 9b83585

Please sign in to comment.