- From: Mark S. Miller <erights@google.com>
- Date: Sat, 27 Apr 2013 18:15:44 -0700
- To: Mark Miller <erights@gmail.com>
- Cc: David Sheets <kosmo.zb@gmail.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: <CABHxS9jOoWk_NHA38r9AaZ_q1wyYJ6vvu5VX0W1+QdUF44cYxw@mail.gmail.com>
On Sat, Apr 27, 2013 at 5:46 PM, 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. > > 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". > Should be: '...so I'll *call* 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 > -- Cheers, --MarkM
Received on Sunday, 28 April 2013 01:16:13 UTC