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

Re: A Challenge Problem for Promise Designers

From: Mark S. Miller <erights@google.com>
Date: Sat, 27 Apr 2013 18:15:44 -0700
Message-ID: <CABHxS9jOoWk_NHA38r9AaZ_q1wyYJ6vvu5VX0W1+QdUF44cYxw@mail.gmail.com>
To: Mark Miller <erights@gmail.com>
Cc: David Sheets <kosmo.zb@gmail.com>, es-discuss <es-discuss@mozilla.org>, "public-script-coord@w3.org" <public-script-coord@w3.org>, Ron Buckton <rbuckton@chronicles.org>, David Bruant <bruant.d@gmail.com>, "Tab Atkins Jr." <jackalmage@gmail.com>, Dean Tribble <tribble@e-dean.com>
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 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.
> 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".

Should be: '...so I'll *call* 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?
> 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.
> 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 ;).
> [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.
> On Sat, Apr 27, 2013 at 11:07 AM, David Sheets <kosmo.zb@gmail.com> wrote:
>> On Sat, Apr 27, 2013 at 6:05 PM, Mark S. Miller <erights@google.com>
>> wrote:
>> > On Sat, Apr 27, 2013 at 9:55 AM, David Sheets <kosmo.zb@gmail.com>
>> wrote:
>> > [...]
>> >>
>> >> I think the major point of confusion in these discussions is the
>> >> result of the framing of the discussion in terms of "flattening". I
>> >> believe most beneficial viewpoint is that of "autolifting".
>> >>
>> >> That is, the exceptional case is not when the function argument of
>> >> "then" returns a Future+ that gets "flattened" but rather when the
>> >> function argument of "then" returns a non-Future that gets
>> >> automatically lifted into a Future.
>> >>
>> >> This change in perspective is non-obvious because in many of these
>> >> APIs there is no succinct lifting operation to make a Future from
>> >> another value. This is a major reason why something like Future.of
>> >> (Future.accept) is important.
>> >
>> > I was following you until this last paragraph. As you define
>> autolifting in
>> > the first two paragraphs, Q(x) would be an autolifting operation. It
>> has the
>> > signature:
>> >
>> >     promise<t> -> promise<t>
>> > or
>> >     t -> promise<t> // if t is not itself a promise type
>> >
>> > Are you distinguishing "autolifting" vs "lifting"? If so, why do you
>> think
>> > it is important or desirable to provide a lifting operation (as opposed
>> to
>> > an autolifting operation)?
>> Yes. Autolifting is conditional on promise-ness. Lifting is fully
>> parametric.
>> If the standard uses autolifting instead of recursive flattening, many
>> of the headaches with "thenables" go away and we gain enormous
>> flexibility in future interoperation with the spec.
>> For instance, if your code might manipulate objects which have
>> callable "then" fields but which don't subscribe to the promises spec,
>> it is safest to always use:
>> return Promise.of(myMaybeNonPromiseThenable);
>> This greatly reduces the criticality of the "is this a promise?"
>> predicate because in most cases you will simply return a non-thenable
>> (autolifted) or a promise-like thenable and not care. In those cases
>> where you wish to put non-promise thenable inside of a promise or
>> *don't know if someone else will want to*, the explicit use of the
>> lifting operation lets you avoid autolifting/flattening.
>> This massively simplifies the protocol between the promises spec and
>> those values it encapsulates by only ever making a single assumption
>> that then-returned thenables are promise-like but their contents are
>> *totally opaque*.
>> I believe this design results in the maximal flexibility and safety
>> for the platform by supplying a handy autolifting "then" while also
>> allowing people to easily subscribe to promise interaction (by
>> then-returning a thenable), defend their thenables from magical
>> unwrapping (by explicitly using Promise.of), and write completely
>> polymorphic code.
>> With this design, in the most common case, developers won't have to
>> use Promise.of. Perhaps the only common use will be in starting a
>> promise chain from a constant:
>> var promise;
>> if (just_use_a_constant) { promise = Promise.of(6); } else { promise =
>> getAsyncValue(); }
>> promise.then(function (x) { return x*2); });
>> While those who translate code from other languages, target their
>> compilers to JS Promises, write polymorphic libraries, use
>> non-promises with callable "then" fields, or invent new interoperable
>> promise-like (but distinct) semantics won't have to worry about
>> hacking around recursive unwrapping.
>> To me, having some standard for promise-like objects in the platform
>> seems very fundamental for handling asynchrony, ordering, failure,
>> need, and probability. If we consider promise-like objects as
>> fundamental, we should investigate the properties of their operations:
>> With recursive flattening "then" operation, the time complexity of
>> "then" is O(n) with n the number of nested promise-like objects.
>> With autolifted "then" operation, the time complexity of "then" is O(1).
>> Here, I am using time complexity as a proxy for the mental complexity
>> of the operation and not as a proxy for execution performance
>> (recursive unwrapping is usually of static depth as we have seen). You
>> can see that not only does the recursive flattening involve a hidden
>> loop that the advanced programmer must reason about but also invokes
>> the notion of "promise-like object" which, as we have seen, leads to
>> all sorts of tangents regarding how to characterize the property of
>> promise-ness and still maintain clarity, safety, extensibility, and
>> ease-of-use.
>> I hope this explanation satisfies you. If not, I am more than happy to
>> answer any questions you may have about this approach.
>> Warm regards,
>> David Sheets
> --
> Text by me above is hereby placed in the public domain
>   Cheers,
>   --MarkM

Received on Sunday, 28 April 2013 01:16:13 UTC

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