We’ve all heard about callback hell, of javascript fame. Using callbacks allows us to chain together actions that might fail using continuation style, that is to say, in addition to arguments, functions are also given functions to run when they succeed or fail. People do not like this b/c every callback increases total indentation – and programmers hate indentation.
function example() {
fetchA((a) => {
fetchB(a, (b) => {
fetchC(b, (c) => {
fetchD(c, (d) => {
// do something with d
})
})
})
})
}
Consider an alternative to continuation passing style: async/await.
async function example() {
const a = await fetchA()
const b = await fetchB(a)
const c = await fetchC(b)
const d = await fetchD(c)
// do something with d
}
This is fine but it runs afoul of the (function coloring
problem)[https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/].
The fetch functions are now not normal functions but async
ones and they do not compose with non-async functions. This
tension has resulted in many JS libraries providing both synchronous and
promise-based apis. Boo.
Ok, so we want neither coloring nor too much indentation. Callbacks don’t change function color and they only add indentation b/c of JS syntax rules. Let’s look at some languages that handle callbacks without adding indentation.
Haskell’s do notation:
example :: IO ()
example = do
a <- fetchA
b <- fetchB a
c <- fetchC b
d <- fetchD c
-- do something with d
Gleam’s use syntax
pub fn example() -> Result(String, Nil) {
use a <- fetch_a
use b <- fetch_b(b, _)
use c <- fetch_c(c, _)
use d <- fetch_d(d, _)
// do something with d
}
Both do and use notation desugar to vanilla
nested callbacks.