Hacker News new | ask | show | jobs
by bazoom42 654 days ago
How is async-await monads? Isn’t it just syntax sugar over callbacks?
2 comments

Consider async-await a syntactic sugar over Promises (from JavaScript). Then, Promises constitute an instance of the Monad typeclass where monadic `bind` or (>>=) is `Promise.then()`, and `return` is `Promise::resolve()`.

Here is a translation of a modification of the example given in [1]:

    const promise1 = Promise.resolve(123);

    promise1.then(v => v * 2).then((value) => {
      console.log(value);
      // Expected output: 246
    });
into Haskell:

    ghci> let promise1 :: IO Int = return 123
    ghci> promise1 >>= (return . (* 2)) >>= print
    246
One key discrepancy worth pointing out is that in the `Promise.then()` API of JavaScript, the function provided to `then` (e.g. `v => v * 2` above) is implicitly composed with a call to `::resolve` in order to turn that function's pure return value, the `Number` 246, into a Promise resolving into the `Number` 246; in Haskell, this operation must be made explicit (hence the composed function `return . (* 2)` passed to the first application of (>>=) or `bind`).

You could say that the instance method `Promise.then()` expects an argument of type (a -> b), with the return value of type `b` being implicitly and automatically wrapped into a Monad `m b`, whereas Haskell's bind expects an argument of type (a -> m b) on that operator's right-hand side, with the return value explicitly wrapped into a Monad `m b` by the provided function argument itself.

[0] https://wiki.haskell.org/Monad

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Maybe I’m confused, but I dont see how Promise.then() corresponds to bind? If I understand correctly, the point of the bind function is you pass a callback which itself return the monad type. But the Promise.then() callback should not return a monad but just the regular result value of invoking the callback.

So in essence Promise.then() is like Array.map() while bind is like Array.flatMap()

Edit: It seems you are correct, you can indeed return a promise in the callback and it compose correctly. But in your example the callback does not return a promise and is therefore not an example of monadic bind.

I had a caveat in my original post re. the implicit wrapping of the `Promise.then()` callback's (pure) return value in a new Promise, and how this differs from Haskell's monadic bind; I had hoped to make a loose analogy while pointing out the differences for the sake of illustration. However it is indeed also possible to return a Promise from `.then()`'s callback, which is closer to Haskell's bind: [0]

    The behavior of the returned promise (call it p) depends on the handler's
    execution result, following a specific set of rules. If the handler function:

    * returns a value: p gets fulfilled with the returned value as its value.

    [...]

    * returns an already fulfilled promise: p gets fulfilled with that promise's value as its value.
... then you can obtain a solution closer to the Haskell translation by using the behaviour of the second cited bullet point from the MDN article:

    const promise1 = Promise.resolve(123);

    promise1.then(v => Promise.resolve(v * 2)).then((value) => {
      console.log(value);
      // Expected output: 246
    });
[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
This article argues that while promises support the required operations for monad, they do not support the monad laws:

https://www.siawyoung.com/promises-are-almost-monads/

It's a good point. `Promise::resolve()` "flattens nested layers of promise-like objects (e.g. a promise that fulfills to a promise that fulfills to something) into a single layer." [0]

The example was meant to be more of an illustrative analogy than an exact correspondence.

[0] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

At that moment, the student became enlightened.
Callbacks are not the same as monads if that is what you are implying.
That's right, but only because it's a 1-to-many comparison error.

Callbacks are one particular monad, not monads in general: https://jsdw.me/posts/haskell-cont-monad/

But then CPS machinery is often used to implement Monads.

> Programming with monads strongly reminiscent of continuation—passing style (CPS), and this paper explores the relationship between the two. In a sense they are equivalent: CPS arises as a special case of a monad, and any monad may be embedded in CPS by changing the answer type.

https://cs.uwaterloo.ca/~david/cs442/monads.pdf

Promises in JavaScript are not monads as far as I can tell. While they have a somewhat similar interface, they do not appear to obey the monad laws.
There is one particular edge case in which they do not satisfy the laws. That happens to make them much more practical in day to day coding than a strict interpretation would be.
So having Promises be monads would make them less useful? This is an interesting point.

It seems to confirm the argument that monads are not that useful or pervasive outside of Haskell.

I was not actually thinking of Javascript (or any particular language) when I mentioned async-await. I was just referring to the concept.