Using the terminology from Mark Miller's "The Paradox of Partial Parametricity", this gist shows how you can build unabashed monadic promises on top of Q-like promises.
Given:
Q: Ref<t> → Promise<t>
then: (Promise<T>, T → Ref<u>) → Promise<u>
defined in the normal way, we want to produce
unit: T → MonadPromise<T>
bind: (MonadPromise<T>, T → MonadPromise<U>) → MonadPromise<U>
such that the monad laws are satisfied.
(Note that for convenience in what follows, we write p.then(f)
instead of then(p, f)
.)
The definitions are simply:
function unit(x) {
return Q({ x });
}
function bind(m, f) {
return m.then(({ x }) => f(x));
}
Since f
's signature is T → MonadPromise<U>
, let f
be given as
f: y ⟼ Q({ x: F(y) })
Then
bind(unit(a), f)
≡ bind(Q({ x: a }), f)
≡ Q({ x: a }).then(({ x }) => f(x))
≡ Q(Q({ x: F(a) }))
≡ Q({ x: F(a) })
≡ f(a)
Since a
is of type MonadPromise<T>
, let a
be given as
a ≡ Q({ x: A });
Then
bind(a, unit)
≡ a.then(({ x }) => unit(x))
≡ a.then(({ x }) => Q({ x }))
= Q({ x: A }).then(({ x }) => Q({ x }))
≡ Q({ x: A })
≡ a
Similarly to before, let a
, f
, and g
be given as
a ≡ Q({ x: A })
f: y ⟼ Q({ x: F(y) })
g: y ⟼ Q({ x: G(y) })
Then
bind(a, f)
≡ a.then(({ x }) => f(x))
≡ Q({ x: A }).then(({ x }) => Q({ x: F(x) }))
≡ Q({ x: F(A) }),
so that
bind(bind(a, f), g)
≡ bind(Q({ x: F(A) }), g)
≡ Q({ x: F(A) }).then(({ x }) => g(x))
≡ Q({ x: F(A) }).then(({ x }) => Q({ x: G(x) }))
≡ Q({ x: G(F(A)) }).
We also have that
bind(f(z), g)
≡ f(z).then(({ x }) => g(x))
≡ Q({ x: F(z) }).then(({ x }) => Q({ x: G(x) }))
≡ Q({ x: G(F(z)) }),
which immediately leads to
bind(a, z => bind(f(z), g))
bind(a, z => Q({ x: G(F(z)) }))
≡ a.then(({ x }) => Q({ x: G(F(x)) }))
≡ Q({ x: A }).then(({ x }) => Q({ x: G(F(x)) }))
≡ Q({ x: G(F(A)) }),
thus giving the desired equality.
As Mark says,
The main failure mode of standards bodies is to resolve a conflict by adding the union of the advocated features.
If we take this lesson to heart, we must choose between Q-like promises and unabashed monadic promises.
Although there have been one or two outspoken voices repeatedly demanding that unit
and bind
are correct, in my opinion, standardizing on the minimal, time-tested pattern of Q-like promises makes the most sense. As demonstrated above, building unabashed monadic promises on top of Q-like promises is quite simple, so those one or two voices can easily achieve their goals with a minimum of extra code. But when deciding which should be baked into the platform and which should require a minimal amount of extra user-space code, I think the many years of experience with Q-like promises' usefulness makes the choice clear.
After some time, if the unabashed monadic promises community has shown the value of the unit
and bind
functions to the same extent the Q-like promise-using community has shown the value of their Q
and then
functions, then perhaps it would be worth providing those with the platform. But that day seems a ways away.