Fwd: A Challenge Problem for Promise Designers

David Sheets kosmo.zb at gmail.com
Sat Apr 27 11:07:07 PDT 2013


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


More information about the es-discuss mailing list