Skip to content

Instantly share code, notes, and snippets.

@domenic
Last active December 17, 2015 15:29
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save domenic/5632079 to your computer and use it in GitHub Desktop.
Save domenic/5632079 to your computer and use it in GitHub Desktop.
Unabashed Monadic Promises on top of Q-Like Promises

Unabashed Monadic Promises on top of Q-Like Promises

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.

Problem Statement

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 Construction

The definitions are simply:

function unit(x) {
    return Q({ x });
}

function bind(m, f) {
    return m.then(({ x }) => f(x));
}

Proof That These Obey the Monad Laws

bind(unit(a), f) ≡ f(a)

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)

bind(a, unit) ≡ 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

bind(bind(a, f), g) ≡ bind(a, z => bind(f(z), g))

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.

Concluding Words

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment