A Challenge Problem for Promise Designers

Tab Atkins Jr. jackalmage at gmail.com
Sat Apr 27 15:49:05 PDT 2013


On Sat, Apr 27, 2013 at 2:21 PM, Dean Landolt <dean at deanlandolt.com> wrote:
> On Fri, Apr 26, 2013 at 11:18 AM, Andreas Rossberg <rossberg at google.com>
> wrote:
>> On 26 April 2013 16:25, Dean Landolt <dean at deanlandolt.com> wrote:
>> > The fundamental controversy, as Juan just noted, is how to precisely
>> > identify a promise in order to do either of these two things. This
>> > problem
>> > isn't quite so clean cut, but it's much more important to solve. I've
>> > been
>> > trying to bring some attention to it over the last few days -- I hope
>> > it's
>> > clear that a `then` method is not enough to identify a promise language
>> > construct -- this will subtly break existing code (e.g. casperjs).
>>
>> Let me note that this is not the fundamental controversy (not for me,
>> anyway). The fundamental controversy is whether there should be any
>> irregularity at all, as is unavoidably introduced by implicit
>> flattening. The problem you describe just makes the negative effect of
>> that irregularity worse.
>
> It may be a little late (I'm just catching up on these promise megathreads)
> but I was suggesting that the irregularity posed by flattening is only a
> distraction. AFAICT you're concern with flattening is actually in regard to
> resolution semantics, right? Otherwise it's unobservable whether you have a
> Promise<value> or a Promise<Promise<value>>. I'm trying to argue that given
> a reliable branding a one-step resolver is easy and sensible to define (no
> flattening), and a recursive resolver is an obvious extension. Almost
> everyone would use the latter, but I completely agree that the former is
> necessary too. No irregularities, everybody wins.

Given the history of this issue so far, and the persistent
miscommunications, I'd like to make sure that you understand what is
meant by *non*-recursive flattening, which is what we on the monad
side are arguing for.

In a world with non-recursive flattening, like I want, this code:

getAPromise()
  .then(function(x) {
    return 5;
  }).then(function(y) {
    alert(y);
  });

and this code:

getAPromise()
  .then(function(x) {
    return Promise.accept(5);
  }).then(function(y) {
    alert(y);
  });

will both alert "5", not "<object Promise>".

The only way to get it to alert "<object Promise>" is with code like this:

getAPromise()
  .then(function(x) {
    return Promise.accept(Promise.accept(5));
  }).then(function(y) {
    alert(y);
  });

That is, you have to very explicitly double up on Promises.  It
doesn't happen by accident.

It's only this last case, which should be rare unless you're doing it
on purpose, which we're all arguing about.  The recursive-flatten
people want this case (and all higher-stacked cases, like "return
Promise.accept(Promise.accept(Promise.accept(5)));") to all alert "5".

This breaks some useful invariants, though.  It means that Promises
are no longer monads, which prevents you from using monad libraries.
It also means that you have to hack around the behavior in other cases
- if you omit a callback from .then(), it's supposed to just "pass
through" for that case, so the next .then() call to provide the
callback gets the exact same value as it would if the callback were
moved up.  This doesn't work with recursive-flattening, though - if
you make a nested Promise explicitly, and then call .then(cb) on it,
the callback will get a promise as an argument, but if you call
.then().then(cb), the promise will have been flattened out.  You have
to insert a special hack into the resolution mechanics to make this
not happen.

Since nested promises are hard to create unless you're doing it on
purpose, can be useful, and produce more consistent semantics overall,
we should be using non-recursive flattening. The only place where you
want recursive flattening is when you're *assimilating an
outside-world promise* from a foreign library, which should require an
explicit action (so you don't end up "flattening" something like a
Casper.js value, and getting nonsense out as a result).  We can just
follow Promises/A+'s flattening semantics, but invoke them from some
explicit function.  Best of all worlds.

> It's been a few years but I recall an exchange we had where he took the
> position that there shouldn't even be a method to test whether a value is a
> promise -- IIRC he was arguing that any `value | Promise<value>`
> functionality was unnecessary, and even hazardous. I was never clear on
> exactly how this could be made to work, especially in interoperable
> libraries, but as a language construct I suppose it's feasible. I'm curious
> to hear where Dave stands now -- he eventually built support for thenables
> into task.js (which I think was what sparked this exchange) but that could
> have been out of convenience. Of course, with this approach I can't imagine
> how promises could possibly be made nestable with one-step resolution (what
> people seem to be calling "monadic" now?).

This is the position that E takes, but it has special language support
for promises, and does the unwrapping by itself, automatically.
That's fine, and it produces a non-monadic form of promises.  JS
likely can't do that, though - it's stuck with promises as "real"
things, distinct from the values they wrap, so it should make the best
of it and make them monadic.

(If you don't understand the term "monadic", look at my earlier post
in this thread, where I gave a short primer.  If you're still having
trouble, let me know privately, and I'd be glad to explain it in more
detail.)

~TJ


More information about the es-discuss mailing list