- From: David Sheets <kosmo.zb@gmail.com>
- Date: Sun, 28 Apr 2013 03:47:45 +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 3:41 AM, David Sheets <kosmo.zb@gmail.com> wrote: > 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 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
Received on Sunday, 28 April 2013 02:48:36 UTC