We just don’t call them monads.
We just don’t call them monads.
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...
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.
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...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...
Callbacks are one particular monad, not monads in general: https://jsdw.me/posts/haskell-cont-monad/
> 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.
It seems to confirm the argument that monads are not that useful or pervasive outside of Haskell.
But what your describing is the actual value proposition of monad though! It's literally the construct you use to avoid excessive unwraps.
Look at the type signature of bind:
m a -> (a -> m b) -> m b
It returns m b. Not m (m b). Not m (m (m b)).> but they aren’t straightjacketed into the ivory tower “laws” in absolutely every edge case
In other words, you can't abstract over them. If you have libraries that manipulate monads, they will be incompatible or buggy, because they will assume (by design) behaviour that a non-monad will not live up to.
I can assure you that JS libraries work with Promises just fine.