A Challenge Problem for Promise Designers

David Sheets kosmo.zb at gmail.com
Sat Apr 27 19:47:45 PDT 2013


On Sun, Apr 28, 2013 at 3:41 AM, David Sheets <kosmo.zb at gmail.com> wrote:
> On Sun, Apr 28, 2013 at 1:46 AM, 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.
>
> But promises aren't thenable? Why are they special?
>
>> 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).
>
> If ((
> f : a -> b => a -> Promise<b> if b != Promise<c> for all c
> but
> f : a -> b => a -> b if b = Promise<c> for all c
> ) for all f under "then")
> is "autolifting"
>
> Then ((
> f : a -> Promise<b> => a -> Promise<b> if b != Promise<c> for all c
> but
> f : a -> Promise<b> => a -> Promise<c> if b = Promise<c> for all c
> ) for all f under "then")
> is "autojoining"
>
> Because "join" : M M t -> M t
>
> (properly, "autolifting" should convert a -> b into Promise<a> ->
> Promise<b> and Promise<a> -> Promise<b> into Promise<a> -> Promise<b>
> but we can probably ignore this earlier mis-take I made so long as we
> can agree on the behavior; "return autoboxing" might have been a
> better terminological choice)
>
>> "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 agree. We should adopt precise definitions to avoid further
> confusion. To that end, I  propose we formally define "flattening" to
> be recursively autojoining to a fixpoint.
>
> That is,
>
> assimilation : thenable :: flattening : promise
>
> This is because flattening implies things are, well, flat (one-level
> and no more).
>
>> 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?
>
> Let's use P<t> for the promise type constructor and T<t> for the
> thenable type constructor.
>
> Suppose we evaulate y = Q(x : P<T<P<T<P<t>>>>>). [which should really
> never happen but ignore that, this is for edification]
>
> Does Q simultaneously flatten and assimilate its argument to a
> fixpoint? Afaict, yes, because promises are a subset of thenables. Is
> y : P<t>?
>
> I agree that Q is also overloaded as an autolifter such that Q : t ->
> P<t> for simple t and Q : P<t> -> P<t> for simple t.
>
>> 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.
>
> To do autolifting, you must have a predicate that distinguishes P<t> from T<t>.
>
>> 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.
>
> This assumes that promises are somehow special and beyond construction
> by the programmer. This immaculate distinction troubles me because it
> immediately closes off the ability to construct systems which are
> peers to promises. That is,
>
> promise.then(x => return unblessedPromise()).then(y =>
> alert(isUnblessedPromise(y)))
>
> alerts "true" because unBlessedPromise() was lifted into
> P<UnblessedPromise<t>> instead of being helpfully unwrapped and then
> lifted into Promise<t>. (I should note here that dynamically
> dispatching the subsequent "then" to UnblessedPromise<t> is an
> interesting possible design decision and probably necessary to
> maintain associativity. This could be overridden by return
> unblessedPromise().then(Promise.of);)
>
> That is the behavior of the above is identical to the behavior of
>
> promise.then(x => return Promise.of(unblessedPromise())).then(y =>
> alert(isUnblessedPromise(y)))
>
> which is quite disappointing.
>
> Ideally, any standardization of this facility would define a simple
> object protocol and a set of equivalences of operations over objects
> with types in that protocol's class. If a promise system which
> seamlessly interoperates with Official Promises cannot be constructed
> by a programmer (no object I can construct is true for the isPromise
> predicate), we will have lost a great deal. If this occurs, any
> JavaScript environment which enforces this blessed/unblessed
> distinction will no longer allow developers to construct many
> important libraries and compatible variations of promises (e.g. I want
> to centrally and conditionally enable logging for all promise
> fulfillments).
>
> If you allow interoperability, then it must be decided how to signal
> what it *is* to *be* a promise and all promises must exhibit certain
> properties. In fact, I would go so far as to say that any /true/
> standard should define both the contract of a promise-able and the
> specific semantics that its flavor of promise-able implements.
>
> From this point of view, it is impossible to mandate no
> promises-for-promises as there is, by definition, no way to tell if a
> given promise is yours or mine. This is an important invariant and
> crucial for the future health and extensibility of this feature.
>
> Whether adherence to that contract occurs by checking
> isCallable(x.then) or in some other manner, it does not matter (except
> for compatibility with existing libraries which use that predicate as
> typeclass advertisement). In all circumstances, no recursive
> operations over promises or thenables should be mandated. Autojoining
> and autolifting are OK and, as you observe, lifting and joining cannot
> truly be prohibited. Assimilation or flattening are useful
> operation(s) when explicitly invoked; however, it should not occur in
> regular use of a promise.
>
> I feel we have made great progress. I hope it is clear now that no
> built-in (automatically in, e.g., "then") unwrapping or assimilation

s/unwrapping or assimilation/flattening or assimilation/ of course.

> should occur. If that is settled, the only remaining issue is over the
> signal to indicate that something is a promise whether built-in or
> user-defined.
>
> Best wishes,
>
> David


More information about the es-discuss mailing list