The Paradox of Partial Parametricity

Tab Atkins Jr. jackalmage at gmail.com
Fri May 24 10:42:29 PDT 2013


On Thu, May 23, 2013 at 10:18 PM, Mark S. Miller <erights at google.com> wrote:
> I went into more depth on this matter in a presentation I just did at TC39.
> The slide deck is at
> <http://wiki.ecmascript.org/lib/exe/fetch.php?id=strawman%3Aconcurrency&media=strawman:promisesvsmonads2.pdf>.
> It was written to accompany a verbal explanation, which it did, but not to
> be self-explanatory. I will try to find the time to explain it, but not
> today. I'll be posting more on this soon. Nevertheless, I think y'all will
> get something out of the slideshow prior to this explanation.
>
> TC39 did not yet come to an official consensus. That said, the emerging
> winner in the room was clearly AP3 from slide19 of the slide deck, which
> Alex has revised Promise.idl to follow. It was clear from the positions in
> the room that we needed both lifting and autolifting in order to achieve
> consensus. In the room I was agreeable to AP3 as well. I remain so.
>
> AP3 only does recursive unwrapping on the return side of .then. AP2, based
> on your post from yesterday, does recursive unwrapping on both sides. I
> stated that I prefer the AP2 style recursive unwrapping on both sides, but
> the advocates for use of .fulfill (unconditional lifting) strongly favored
> AP3. I will let them speak for themselves.
>
> Even after sleeping on it, I can live happily with AP3. It is actually even
> better at supporting QP style than I appreciated yesterday. In fact, for
> good QP style code, it is equivalent to AP2. I am satisfied that either in
> AP2 or AP3, autolifting is coherent in the presence of lifting since the
> presence of lifting will be invisible to good QP style code.
>
> As for whether lifting is coherent in the presence of autolifting in AP3, I
> will let the lifting advocates speak for themselves.
>
> We may not have made the best choice, but we didn't make sausage.

Unfortunately, AP3 is incoherent.  I'd've thought you'd like it less.  ^_^

AP3 (recursive unwrapping of the return value of .then()) doesn't give
Q people the guarantees they want (.then() callbacks always receive a
plain value), nor does it give monadic people the flexibility they
want (Domenic's proposal is, I'm sorry, abhorrent in its details -
I'll provide more reasoning at the end of this email).  It also adds a
*useless* fulfill method - you can't actually *use* the fact that the
promises are nested at all, which is just plain silly.  AP3 is
straight-up consensus-exhaustion, where people weren't thinking the
details through sufficiently to realize the details weren't coherent.

The correct solution is AP2, but with recursive unwrapping on the
*read* side for .then().

This is much better than AP3.  You note that AP2 and AP3 are
equivalent for "good QP style code".  If you flip it so it just
unwraps on the read side, it's equivalent for *all* QP-style code -
even if the surrounding code is passing around nested promises like
it's going out of style, your QP-style code can safely rely on the
guarantee that your callbacks will never receive a promise as their
argument.

What's more, if you're using .then() for things, but you're passing in
a callback from elsewhere that assumes it can return nested promises,
everything's still kosher!  If you chain another .then() off of it,
you'll still get a plain value; if you chain a .chain()/.flatMap() off
of it, you'll get the "real" return value of the inner promise.  This
wins for everyone, in every situation I've been able to think of.

I'm sorry that I'm not a TC39 member, and that the talks were prepared
before the conversations earlier this week that led me to discover
this particular combination that works best.  I think I could've
influence the conversation better had I been there. :/

Details about why Domenic's solution is unfortunately terrible:

1. It only looks remotely passable because it *relies* on argument
destructuring to mostly hide the fact that it's abusing a useless
wrapper object solely to defeat the recursive unwrapping. If you
removed that and used ES5 argument lists, the ugliness would be
manifest.

2. Domenic uses "{x}" in his example code, which looks like a standard
placeholder argument name, but it's *not*.  "x" is actually *part of
the contract of the proposal*, and *must* be used by all callbacks to
interoperate.  If they want to name their argument something else,
they have to use the longer "{x: foo}" form, making it less convenient
and more obviously hacky.  (In your slides, you change the name to
"value".)

3. Domenic's "promises" are, in practice, a *completely different
beast* from normal promises, which don't interoperate at all.  If
you've got a .then-based workflow, and a method tries to return a
Domenic-promise, you'll just break - you're expecting a plain value,
not an object with a .value property containing the actual plain value
(or maybe containing another object with .value, containing another
object with .value, containing the plain value).  The same applies in
reverse - calling .chain/.flatMap() on a normal promise either fails
(because you only put the function on a subclass) or it breaks
(because you're trying to decompose the argument, but it's a plain
value, not an object with a .value method).

~TJ


More information about the es-discuss mailing list