- From: Mark Miller <erights@gmail.com>
- Date: Sat, 27 Apr 2013 17:46:46 -0700
- To: David Sheets <kosmo.zb@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>
- Message-ID: <CAK5yZYgdvexmAhdsixxxBq-yT7rVJObDnv79UzSu=BonecRYfw@mail.gmail.com>
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". 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@gmail.com> wrote: > On Sat, Apr 27, 2013 at 6:05 PM, Mark S. Miller <erights@google.com> > wrote: > > On Sat, Apr 27, 2013 at 9:55 AM, David Sheets <kosmo.zb@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
Received on Sunday, 28 April 2013 00:47:13 UTC