Killing `Promise.fulfill`

Mark S. Miller erights at google.com
Wed Aug 21 13:57:03 PDT 2013


Excellent excellent! It seems we are in agreement and clarity on all
terminology issue, and on almost complete agreement on substantive issues.
The only remaining issue is whether either of the following expressions
always return a fresh promise, and if they are exactly equivalent:

a) Promise.resolve(v1)
b) new Promise(r=>r.resolve(v1))

When v1 is a promise,
x) I prefer that #a return v1 and #b return a fresh promise that adopts v1.
y) You prefer that both #a and #b return a fresh promise
z) The fewest feasible allocations would have both return v1.
w) I do not expect anyone will want #a to return a fresh promise and #b to
return v1.

We both agree that if we settle on either #y or #z, then #a and #b are
equivalent. Otherwise they are equivalent up to this issue.

The reason I prefer that #b return a fresh promise is that it is surprising
for a "new" expression to return something other than a fresh object.

The reason I prefer #a is that the primary use case for #a is coercion --
given possible promise v1, gimme a guaranteed genuine promise that
designates the same (possibly eventual) non-promise. See calls to the Q()
function at http://wiki.ecmascript.org/doku.php?id=strawman:concurrency and
http://es-lab.googlecode.com/svn/trunk/src/ses/contract/ (which corresponds
to http://research.google.com/pubs/pub40673.html ). These calls are
numerous and necessary, and sufficiently frequent that the extra transient
allocation in the typical case will be a noticeable cost.

If this is indeed the only outstanding issue, I think this overall thread
can declare victory, noting that this one detail remains to be, um,
resolved ;).




On Wed, Aug 21, 2013 at 1:24 PM, Tab Atkins Jr. <jackalmage at gmail.com>wrote:

> 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:
> >> 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.
>
> Well, one is lower-level than the other, so I think it makes sense to
> think of it my way. ^_^
>
> >> 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.
>
> Ignoring the storage issue, the point of this thread is that the
> current accept/resolve semantics are *indistinguishable* for .then(),
> and unnecessary/confusing for .flatMap().
>
> It's fine to use "resolve" in the A+ sense even if the technical
> definition is just "puts an arbitrary value in the promise", because
> you can't tell the difference from .then().
>
> > 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.
>
> Ah, this is why I thought you wanted a deep flattening operation -
> adoption semantics aren't quite what Promises/A+ uses.
>
> I guess I'm okay with resolve() adopting promises and just fulfilling
> with other values, if we still have an op which solely puts a value
> into the promise.
>
> >>  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?
>
> Yes, the mechanics of what happens are clear and agreed-on.  But that
> doesn't mean that the action matches a useful state.  I think the only
> useful things to call "states" are the observable ones where a
> particular callback is called.  Any other states we might invent may
> be useful for talking about things internally, but that's of lesser
> importance.
>
> >> 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".
>
> "fulfilled or rejected" is different from "contains a value" in our
> context, because a promise can contain a pending promise.  This will
> still call .flatMap() callbacks.  Promises/A+'s use of "settled" to
> mean "fulfilled or rejected" is fine with me.
>
> >> 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.
>
> Yes, your latter implication is precisely what Domenic started this
> thread with, and what I've been talking about so far in this thread.
>
> >> 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.
>
> Yeah, if "pending" means "not fulfilled", which is the Promises/A+
> meaning, then this is true.  I'm fine with that.
>
> > 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.
>
> I don't think we want Promise.resolve() to only sometimes return a new
> promise.
>
> >> 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.
>
> Yeah, makes sense.
>
> >> (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 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.
>
> I meant it in the strict sense - that's literally a desugaring of
> Promise.resolve().
>
> ~TJ
>



-- 
    Cheers,
    --MarkM
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130821/eb8decee/attachment-0001.html>


More information about the es-discuss mailing list