A Challenge Problem for Promise Designers

Tab Atkins Jr. jackalmage at gmail.com
Sat Apr 27 20:31:45 PDT 2013


On Sat, Apr 27, 2013 at 5:46 PM, Mark Miller <erights at 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


More information about the es-discuss mailing list