W3C home > Mailing lists > Public > public-script-coord@w3.org > April to June 2013

RE: A Challenge Problem for Promise Designers

From: Ron Buckton <rbuckton@chronicles.org>
Date: Mon, 29 Apr 2013 18:03:28 +0000
To: Tab Atkins Jr. <jackalmage@gmail.com>, Mark Miller <erights@gmail.com>
CC: David Sheets <kosmo.zb@gmail.com>, "Mark S. Miller" <erights@google.com>, es-discuss <es-discuss@mozilla.org>, "public-script-coord@w3.org" <public-script-coord@w3.org>, David Bruant <bruant.d@gmail.com>, Dean Tribble <tribble@e-dean.com>
Message-ID: <33D2646B20374248B7CAA8F25919AAFC42762C61@BY2PRD0111MB494.prod.exchangelabs.com>
Thanks for the clarifications re: Future as a monad. My understanding of this is as follows (please correct me if I am wrong):

* The expected result of the resolve/reject callbacks passed to Future#then is itself a Future.
* If the result of the resolve/reject callbacks is not a Future it is logically lifted into a Future, by accepting the value on the Future that is the result of Future#then.
* The Future result of the callback is merged into the Future returned Future#then then by chaining to either Future#then (Future#done?) of the callback result. 
* Given this, it is not usually necessary to "recursively unwrap" or "flatten" a Future. As a result Future#then should not flatten the result. This would preserve Future.accept(Future.accept(value)).then().then(F => /* F is Future(value) */).

I can also agree that it is inherently unsafe to assimilate "thenables" for Future.resolve/FutureResolver#resolve, due to possible collisions with the property name on existing objects such as with casper.js. This kind of collision is the same rationale for having an @iterator symbol for iterators, though I wouldn't think the same resolution is likely the best result in this case. I am of the opinion that native Futures should only resolve native Futures (or possibly their subclasses). To assimilate you could have a Future.of (or possibly Future.from to match Array.from for "array-like" objects), which would assimilate "future-like" objects (i.e. "thenables"), but only via one level of unwrapping and not recursively.

I'm still concerned about cancellation, but will spin up another thread to hopefully spark some thoughtful discussion on the topic.


> -----Original Message-----
> From: Tab Atkins Jr. [mailto:jackalmage@gmail.com]
> Sent: Saturday, April 27, 2013 8:32 PM
> To: Mark Miller
> Cc: David Sheets; Mark S. Miller; es-discuss; public-script-coord@w3.org; Ron
> Buckton; David Bruant; Dean Tribble
> Subject: Re: A Challenge Problem for Promise Designers
> On Sat, Apr 27, 2013 at 5:46 PM, Mark Miller <erights@gmail.com> wrote:
> > I am worried that we're again separated by a common terminology more
> > than we are by an actual technical disagreement. I am arguing against
> > an unconditional lift operation that would make a promise-for-promise.
> > Or at least seeking to provoke someone to provide a compelling example
> > showing why this is useful[1]. What all the recent messages seem to be
> > arguing for is the existence of a non-assimilating and perhaps a
> > non-unwrapping lift-ish operation, so that one can make a
> > promise-for-thenable. I have no objection to being able to make a
> promise-for-thenable.
> The reasoning *for* an unconditional lift operator is that promises, as
> specified by DOM Futures or Promises/A+, are roughly a monad, and having
> an unconditional lift is required to satisfy the monad laws.
> Being a monad is useful, in ways similar to how being an iterable is useful, or
> an array-like, or many other typeclasses that various languages recognize.
> For example, Future.all() is almost equivalent to a variadic liftA() (name taken
> from Haskell), which takes a function and any number of arguments, all
> wrapped in some particular monad, and executes the function with its
> arguments in the way that the monad prefers.  For promises, that means "if
> every promise accepts, execute the function and return an accepted promise
> for the return value; otherwise, return a rejected promise".  This sort of
> function comes *for free* once a class has been established as a monad, just
> as Python's very useful itertools library applies "for free" to anything that
> establishes itself as an iterable.  You don't have to write any special code to
> make it happen; just the fact that promises are monads means that the
> generically-written liftA() method automatically works.
> > The clearest sign to me of the potential for misunderstanding is the
> > use of the term "flattening" for unwrapping of thenables. To be clear,
> "flattening"
> > is about promises -- it is just the conditional autolifting seen from
> > the other side (as I now understand the term autolifting -- thanks).
> > "unwrapping" is what happens to thenables. "Assimilation" is recursive
> > unwrapping of thenables. I understand that it can be difficult to keep
> > these distinctions straight. I wouldn't be surprised if I've been
> > sloppy myself with these terms earlier in these threads. But now that
> > we're zeroing in, these fine distinctions matter.
> I'm not sure where you got this precise terminology, but I'm willing to adopt
> it for these discussions if it helps reduce confusion.
> > As I demonstrated somewhere in one of the Promises/A+ threads, I don't
> > think it is practical to prohibit these promises-for-thenables anyway.
> > As an example, let's take Q's use of Q as an allegedly fully
> > assimilating lift-ish function. Like an autolifter Q(promise) returns
> > that promise. And Q(x) where x is neither a promise nor a thenable,
> > returns promise-for-x. The controversial part -- which I fully
> > sympathize with since it is a dirty hack
> > -- is that Q(thenable) assimilates -- it does recursive unwrapping
> > (NOT
> > "flattening") of the thenable. Assimilation aside, Q is an autolifter,
> > so I'll can it an "assimilating autolifter". Assume a system is which
> > this Q function and .then are the only lift-ish operations, and that
> > .then is also an assimilating autolifter. What guarantee is supposed to
> follow?
> .then() *cannot* be assimilating, nor can the default autolifter.
> Assimilation needs to be something explicit and separate, or else things will
> mysteriously break when you mix in objects like from Casper.js.
> It's not cool, nor is it necessary, for promises to forever poison the landscape
> of "objects that happen to have a method named 'then'".
> > Assuming that the thenable check is if(typeof x.then === 'function'),
> > intuitively, we might think that
> >
> >     Q(p).then(shouldntBeThenable => alert(typeof
> > shouldntBeThenable.then));
> >
> > should never alert 'function'. But this guarantee does not follow:
> >
> >     var p = {};
> >     Q(p).then(shouldntBeThenable => alert(typeof
> shouldntBeThenable.then));
> >     p.then = function() { return "gotcha"; };
> >
> > This is a consequence of assimilation being a dirty hack. The notion
> > of a thenable is only marginally more principled than the notion of array-
> like.
> > It is not a stable property. Above, p became a thenable after Q
> > already judged it to be a non-promise non-thenable and hence returned
> > a promise-for-p. This promise scheduled a call to the callback before
> > p became a thenable, but p became a thenable before the callback got
> > called. Alleged guarantee broken.
> >
> > Thus, no fundamental guarantee would be lost by introducing an
> > expert-only operation that does autolifting but no unwrapping. This
> > would make convenient what is possible anyway: promises-for-thenables.
> > But this is
> > *not* an argument for introducing a full lifting operation.
> > Introducing that would break an important guarantee -- that there are
> > no promises-for-promises. Without full lifting, promises-for-promises
> > remain impossible.
> Why do think that "no promises-for-promises" is an important guarantee?
> > I leave it to monad fans and/or haters of assimilation to suggest
> > names for this convenient operation, a non-unwrapping autolifter. I'm
> > confident that if I tried to name it, I'd only cause more confusion ;).
> It should just be called "resolve", as in Future.resolve().
> > [1] FWIW, if there's interest, I can provide several examples where a
> > promise-for-promise is useful, but I know of no compelling example.
> > The utility of the examples I have in mind are not worth the cost of
> > introducing this possibility of a promise-for-promise.
> Given the demonstrated difficulty of accidentally creating a promise-for-a-
> promise, why do you feel it is so important to guarantee that it can't ever
> happen?
> ~TJ

Received on Monday, 29 April 2013 18:04:01 UTC

This archive was generated by hypermail 2.4.0 : Friday, 17 January 2020 17:14:13 UTC