- From: David Sheets <kosmo.zb@gmail.com>
- Date: Sun, 28 Apr 2013 03:41:22 +0100
- To: Mark Miller <erights@gmail.com>
- Cc: "Mark S. Miller" <erights@google.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 Sun, Apr 28, 2013 at 1:46 AM, 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. 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 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
Received on Sunday, 28 April 2013 02:48:02 UTC