A Challenge Problem for Promise Designers

Mark S. Miller erights at google.com
Sat Apr 27 18:15:44 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 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 at gmail.com> wrote:
>
>> On Sat, Apr 27, 2013 at 6:05 PM, Mark S. Miller <erights at google.com>
>> wrote:
>> > On Sat, Apr 27, 2013 at 9:55 AM, David Sheets <kosmo.zb at 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
>



-- 
    Cheers,
    --MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/42dcc833/attachment-0001.html>


More information about the es-discuss mailing list