I know that Javascript's promises are technically neither functors nor monads in the sense of Haskell, because (among other things)
- they include a
bind
operation that falls back tomap
when a pure function is passed in (and thus has an ambiguous type) - both the
Promise
constructor andresolve
(akareturn
) join nested promises recursively
The first issue can easily be bypassed by always providing a function with the right type a -> Promise b
.
The second issue obviously violates the parametricity trait of parametric polymorphic functions, i.e. one cannot construct a m (m a)
structure. But what would this structure mean in the context of promises/asynchronous computations? I cannot think of a meaningful semantics for Promise (Promise a)
, where Promise
is a monad. So what do we lose? What are the implications of the recursive joining?
Provided we are pretty pragmatic (and that's what we should be when we're programming Javascript), can't we claim that a Promise
is a monad in Javascript if we take care of the edge cases?
Or by not using
then
as thebind
operation of the monad, but some type-correct ones. Creed is a functionally minded promise library that providesmap
andchain
methods which implements the Fantasy-land spec for algebraic types.The second issue can be bypassed as well with the same approach, by not using
resolve
butfulfill
instead, and the staticof
method as the unit function.It's a promise for a promise for a value. Not every constructible type needs to be "meaningful" or "useful" :-)
However, a good example of a similar type is provided by the Fetch API: it returns a promise that resolves to a
Response
object, which again "contains" a promise that resolves to the body of the response.So a
Promise (Promise a)
might have only one success result value, which could as well be accessed through aPromise a
, however the two levels of promisesNotice that the
Promise
type should have a second type variable for the rejection reason, similar to anEither
. A two-levelPromise err1 (Promise err2 a)
is quite different from aPromise err a
.You haven't mentioned the biggest issue yet, however: they're mutable. The transition from pending to settled state is a side effect that destroys referential transparency if we consider execution order, and of course our usual use cases for promises involve lots of IO that isn't modelled by the promise type at all.
Applying the monad laws is fun and occasionally useful, but we need lots of pragmatism indeed.
Not only in the sense of Haskell, in any other way as well.
there is no
bind
operator provided by JS native promisesI presume you mean unwrapping "theneables", i.e. calling functions stored under
then
prop whenever there is such function.This would not resemble
map
e.g. whenmap(f)
is used forf = x => {then: a => a}
.Indeed.
You need to allow storing arbitrary values. Promises are not allowed to store theneables (without unwrapping), which is the problem. So you need to change semantics of both objects and methods. Allow objects to store theneables without change and implement
.bind
aka.chain
that unwraps (or joins) theneables precisely once - no recursion.This is what
creed
does for promise-like objects andcpsfy
for callback-based (aka continuation passing style) functions.Writing safe, succinct and composable code is pragmatic. Risking introduce subtle bugs via leaky abstractions that might crash critical software with far going consequences is not. Every edge case is a potential source of such risk.
In that respect, claiming that
Promise
is a monad does more harm than help, beside being incorrect. It does harm because you cannot safely apply monadic transformations to promises. E.g. it is unsafe to use any code conforming to the monadic interface with promises as if they were monads. If used correctly, monads are there to help abstracting and reusing your code, not to introduce lines of checking and hunting for edge cases.