domenic at domenicdenicola.com (2013-08-29T19:21:35.002Z)
On Tue, Aug 20, 2013 at 8:55 PM, Mark S. Miller <erights at google.com> wrote: > OTOH, even though it is an allowed behavior for "new" to not return a fresh object, so I think I prefer Should be: "...even though it is an allowed behavior for 'new' to not return a fresh object, it is surprising, so I think I prefer..."
On Tue, Aug 20, 2013 at 8:55 PM, Mark S. Miller <erights at google.com> wrote: > On Tue, Aug 20, 2013 at 11:04 AM, Tab Atkins Jr. <jackalmage at gmail.com>wrote: > >> On Tue, Aug 20, 2013 at 9:18 AM, Mark S. Miller <erights at google.com> >> wrote: >> > To answer this precisely, we need good terminology to distinguish two >> levels >> > of abstraction: The distinctions observable to the AP2.flatMap >> programmer >> > and the coarser distinctions observable to the AP2.then programmer. >> Let's >> > start ignoring thenables and considering only promises-vs-non-promises. >> > Let's also start by ignoring rejection. >> > >> > At the AP2.flatMap level, >> > * for a promise p and an arbitrary value v, p may accept v. p is then >> in >> > the "accepted" state. >> > * for a promise p and a promise q, p may adopt q. p is then in the >> > "adopting" state. >> > Putting these together, we can also say >> > * for a promise p and an arbitrary value v, p is resolved to v if p >> either >> > accepts v or adopts v. p is then in the "resolved" state. >> > >> > p2 = p1.flatMap(v1 => q2) >> > >> > means, if p1 is accepted, then v1 will be what it has accepted. >> > >> > If q2 is a promise, then p2 adopts q2. >> > >> > p2.flatMap(...) fires as a result of acceptance but not adoption. If q2 >> > accepts, then p2 likewise accepts and p2.flatMap fires by virtue of this >> > acceptance. >> > >> > At the P2.then level >> > * for a promise p and a non-promise v, p may be fulfilled with v. p is >> > then in the fulfulled state. >> > * for a promise p and a promise q, p may follow q. p is then in the >> > following state. >> > * Until a promise p is either fulfilled or rejected, it is pending. >> > Putting these together, we can also say >> > * for a promise p and an arbitrary value v, p is resolved to v if >> either p >> > is fulfilled with v or p follows v. p is then in the "resolved" state. >> > >> > p4 = p3.then(v3 => v4) >> > >> > means, if p3 is fulfilled, then v3 will be what p3 is fulfilled with. >> > >> > p4 is resolved to v4. If v4 is a promise, then p4 follows v4. Else p4 is >> > fulfilled with v4. >> > >> > p4.then fires as a result of fulfillment but not following. If p4 >> follows v4 >> > and v4 fulfills, then p4 likewise fulfills and p4.then fires by virtue >> of >> > this fulfillment. >> > >> > Notice that "resolved" is the same states at each level, even though >> these >> > states are described differently. That is why we can use the same term >> at >> > both levels. Likewise, the concept of "unresolved" is meaningful at both >> > levels. >> >> Argh, I knew this would turn into another confusing terminology >> discussion. ^_^ >> > > Indeed ;). > > I think this is because you more naturally think at the AP2.flatMap level > of abstraction and derive AP2.then level concepts from that. And vice versa > for me. That's why I tried to clearly lay out and distinctly name the > concepts relevant at each level. > > > >> >> I'm not quite getting this. Why are you using "resolved" in this way? >> > > Because it corresponds to how "resolved" has historically been used in > Promses/A+ ever since the Promises/A+ distinguished "resolved" vs > "settled". It also describes what .resolve does. Your proposed meaning of > .resolve if what I'm calling .accept. To the AP2.then observer, this also > does the job historically associated with .resolve, but at a prohibitive > storage cost for .then oriented patterns. Using .accept for .resolve would > be much like using a non-tail-recursive language implementation to execute > algorithms written assuming tail recursion optimization. > > The relationship between the two is: > > resolve(v) => { isPromise(v) ? adopt(v) : accept(v) } > > except that I am not proposing that an explicit "adopt" method be added to > the API. > > > >> It doesn't seem to map to a useful state for either mode, since >> you're munging together the case where v4 is a value (p4 can call its >> callbacks) and where v4 is a promise (p4 maybe can't call its >> callbacks yet, or ever, depending on v4's state). > > > I'm just restating the semantics I thought we agreed on. From the AP2.then > perspective p4 resolves to v4. From the AP2.flatMap perspective, if v4 is a > promise, p4 adopts v4. Otherwise p4 accepts v4. > > The only alternative I see is that p4 always accepts v4. This would > accumulate an explicit layer of wrapping for each level of then-return, > since these layers would need to be observable by .flatMap (unless the > implementation can prove that p4 will never be observed with .flatMap, > which is unlikely to be common). > > Regarding the calling of p4.then callbacks, your summary is correct: if v4 > is a non-promise, then p4 fulfills to it and p4.then can call its > callbacks. If v4 is a pending promise, then p4.then cannot yet call its > callbacks. What am I missing? What is being munged? > > > >> You're also munging >> together the case where q2 is pending vs not-pending, which again >> means that either p2 can call its callbacks or not. >> > > I'm using the term "pending" at the AP2.then level, to mean "not fulfilled > or rejected". The similar concept at the AP2 level isn't something we've > previously named, but it means "not accepted". Here I will use "unaccepted". > > So I don't know what you mean by munging. I thought we agreed that p2 > adopts q2, and while q2 is unaccepted, p2 is unaccepted as well and > p2.flatMap cannot call its callbacks. Once q2 becomes accepting, then p2 > becomes accepting as well and p2.flatMap can call its callbacks. Are we in > agreement? > > > >> >> In my email, and I think Domenic in his, I'm trying to nail down some >> terms that map to useful states, capturing observable distinctions in >> behavior: >> >> "resolved" means a promise contains a value - it's no longer pending. >> > > Domenic clarified that the modern term for "no longer pending", i.e., > fulfilled or rejected, is "settled". Perhaps this is the source of > confusion? In E, Waterken, perhaps AmbientTalk, and in historically earlier > versions of the Promises/A+ spec, we used to say "resolved" for what we now > call "settled". We changed this terminology precisely because of the > conflict that the .resolve operation did not cause a promise to be what we > had called "resolved" and now call "settled". > > > >> Your p2 is resolved only when q2 becomes resolved, due to adoption >> semantics. > > > Ok, this hypothesis fits. It is true that > > p2 is settled only when q2 becomes settled, due to adoption > semantics. > > However, the original is not true. Since .resolve either adopts or > accepts, p2 is resolved as soon as it adopts q2. > > OTOH, the previous hypothesis that your "resolved" is my "accepted" also > fits: > > p2 is accepted only when q2 becomes accepted due to adoption > semantics. > > I can guess which rewrite better fits what you're trying to say, but I'll > let you clarify. > > >> (If you were to put q2 directly into another promise, via >> `p2 = Promise.resolve(q2)`, then p2 would be resolved. > > > yes. > > > >> Adoption >> semantics flatten one level, but `Promise.resolve()` isn't adopting.) >> > > This is true for .accept. > > > >> A promise is "resolved" when it would call its flatMap() callbacks. >> > > Again true for "accepted". > > >> >> "fulfilled", taken from Promises/A+, means a promise contains a >> non-promise value, or contains a fulfilled promise. > > > Ignoring thenables as we're doing here, yes. > > > >> Your p4 is only >> fulfilled if v4 is a non-promise value, or is a fulfilled promise. >> > > yes. > > > >> >> So, a promise starts out "pending", becomes "resolved", and then >> becomes "fulfilled". This ordering is always preserved, though some >> states might happen at the same time. >> > > Not quite. A promise starts out pending an unresolved. If p5 is resolved > to p6 and p6 is pending, then p5 is both pending and resolved. > > >> >> If necessary, we can come up with distinct terms for "not resolved" >> > > "unresolved" > > >> and "not fulfilled", > > > "pending" === "not fulfilled and not rejected" > > >> since a promise can be resolved but not >> fulfilled. (This is exactly the state that p4 is in if v4 is a >> non-fulfilled promise.) > > > > > >> "Not fulfilled" = "pending" (Promises/A+ >> meaning) > > > yes, ignoring rejected as we are doing here. > > > >> and "not resolved" = "super pending"? ^_^ >> > > "unresolved" > > >> >> We could use the terms differently than what I've defined here, but why? >> > > I think you're missing one distinction, as you're using "resolved" for > what I'm calling "accepted" and you have no name for what I'm calling > "resolved". By presuming that the accept operation is used for cases that > Promises/A+ uses the resolve, you impose prohibitive storage costs. > > >> >> > I'm not so much concerned with the static .resolve method, since the >> extra >> > storage cost for the static method is negligible. >> > > Replying to myself here, I retract that statement of non-concern, since > the static .resolve method would be the std method to be used the way the Q > function is currently used. The static .resolve method's behavior, in terms > of .accept, is > > Promise.resolve = v => ( isPromise(v) ? v : Promise.accept(v) ); > > This avoids even a transient allocation cost when Promise.resolve is used > to coerce (or auto-lift if you wish) a possible promise into a guaranteed > promise. > > >> However, what does >> > aResolve.resolve do? If it causes its promise to accept, this must be >> > observably different to .flatMap observers than if it causes its >> promise to >> > adopt. This difference is not observable to .then observers, which is >> why >> > I've accidentally missed this issue twice now. But since an >> implementation >> > cannot know ahead of time whether there might be .flatMap observers, >> using >> > .accept for .resolve would impose prohibitive storage costs on .then >> > oriented patterns. See the message Anne linked to. >> >> I'm not entirely certain I get your point, so let me restate in code >> and hopefully clearer text. Given this code: >> >> p1 = Promise.resolve(v1) >> p2 = Promise.resolve(p1) >> >> p2.flatMap(print) would print the p1 object, but p2.then(print) would >> print v1. If p2 was the only thing referring to p1, and we somehow >> knew that you'd only interact with p2 via .then(), we could GC p1 and >> just keep v1 around. However, since we don't know that, we have to >> keep both p1 and v1 around, which is an extra memory cost. >> >> Is this what you were referring to? >> > > Exactly! When going around a tail recursive async loop, these unnecessary > p1s pile up. See Q.async at < > http://wiki.ecmascript.org/doku.php?id=strawman:async_functions#reference_implementation> > for example. > > > >> >> (I have no idea why this paragraph I'm responding to draws a >> distinction between Promise.resolve() and the >> PromiseResolver#resolve() method, though. `Promise.resolve(v1)` is >> exactly identical to `new Promise(r=>r.resolve(v1))` - it's just a >> typing shortcut.) >> > > In that previous message of mine, that distinction was indeed unnecessary. > It was only because I wanted to emphasize the PromiseResolver#resolve case, > as it seemed clearer to me that we can't afford the extra wrapping there. > However, I was wrong. > > Nevertheless, the distinction here may or may not be needed depending on > your answer to a question: In > > new Promise(r=>r.resolve(v1)) > > if v1 is a promise, does this "new" call return v1 itself? Clearly, if the > resolver is only stored during construction and called later, the answer > would be no. The returned promise can only adopt v1 later. But literally in > the code above the resolver's .resolve method is called during > construction, so this seems a sensible option. OTOH, even though it is an > allowed behavior for "new" to not return a fresh object, so I think I prefer > Should be: "...even though it is an allowed behavior for 'new' to not return a fresh object, it is surprising, so I think I prefer..." > the answer "no, the 'new Promise(...)' expression must return a fresh > promise". > > If the answer is yes, then indeed the two expressions mean exactly the > same thing. > > >> >> >> (Note that if anyone thinks we need something that eagerly flattens a >> >> promise, rather than flattening happening implicitly via the >> >> definition of "fulfilled value" for then(), realize that this eager >> >> flattening operation is hostile to lazy promises. While this might be >> >> *useful* in some cases, it's probably not something we need or want in >> >> the core language, or if we do, it should be given an appropriately >> >> descriptive name, rather than yet another synonym for "accept".) >> > >> > I agree that we do not need eager flattening. >> >> I don't understand what you're asking for, then. >> > > Did anything in any of my messages imply that I desire eager flattening? > > > >> >> ~TJ >> > > > > -- > Cheers, > --MarkM > -- Cheers, --MarkM -------------- next part -------------- An HTML attachment was scrubbed... URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130820/540d303f/attachment-0001.html>