domenic / promises-unwrapping

The ES6 promises spec, as per September 2013 TC39 meeting
1.23k stars 94 forks source link

`Promise.as` instead of coercion behavior for `Promise`. #8

Closed domenic closed 10 years ago

domenic commented 10 years ago

Nobody feels strongly that Promise(x) should coerce, and @BrendanEich at least dislikes this.

See http://esdiscuss.org/topic/killing-promise-fulfill#content-55

domenic commented 10 years ago

@erights I do not like the name very much. Perhaps we can bikeshed it here to avoid flooding es-discuss. (Sorry Brendan for tagging you in in the OP; please feel free to mute the thread.)

Consider:

vs.

In fact it seems like this is almost the opposite of what we mean.

Suggestions welcome. Tagging in @kriskowal for his thoughts. I don't have any short ideas; Promise.toPromise is the best I can think of, unless we can get consensus on changing Array.from to behave like we want Promise.from to behave.

erights commented 10 years ago

@domenic Sigh. I see what you mean. Perhaps there's another short word we haven't thought of yet?

kriskowal commented 10 years ago

I imagine we have a deliberate blind spot over Promise.for(value), but we are going for promise.catch, right?

domenic commented 10 years ago

We can always break out the old preposition list: http://www.englishclub.com/grammar/prepositions-list.htm

"via" seems moderately attractive.

@kriskowal That could work, but the fact that Promise.for(promise) === promise is a little bit weird. (In case it wasn't clear from the OP, this operation is equivalent to IsPromise(x) ? x : Promise.resolve(x). Promise.resolve doesn't work because it should probably create a new promise whenever you call it.)

kriskowal commented 10 years ago

"Moderately attractive" conveys my feelings on Promise.via(thenable) exactly.

kriskowal commented 10 years ago

Promise.to(eatVegetables()) reads alright.

erights commented 10 years ago

OMG

domenic commented 10 years ago

Current contenders: Promise.to and Promise.via. Let's see how they fare:

vs.

OK. But now let's take a page from @kriskowal's book and consider some more realistic scenarios.

vs.

Hrm. I don't feel any more enlightened.

annevk commented 10 years ago

I think "to" works if you read it the other way around.

annevk commented 10 years ago

"5 to Promise", "thenable to Promise", etc. Makes perfect sense.

erights commented 10 years ago

We should consider "from" to still be a candidate. Let's try it:

And using the Promise constructor without "new" as a verb:

Altogether, I like "to" best, but "via" and "from" are ok. The Promise constructor as coercer reads best, but I agree with Brendan that we should not add more such double-duty constructors when we don't need to.

erights commented 10 years ago

@annevk If we could read these backwards, we should revive "as". But we can't.

domenic commented 10 years ago

It's a bit unfortunate that Promise.to(x) means more or less the same operation as Array.from(x), despite "to" and "from" being linguistically opposite.

erights commented 10 years ago

Yeah, that disqualifies it IMO.

erights commented 10 years ago

I think I'm coming around to Promise.from :(.

annevk commented 10 years ago

Why is Promise.of() wrong?

Also, do we need the Promise.something(promise) === promise behavior?

Cop-out and leave this to libraries? (Doesn't really seem acceptable.)

erights commented 10 years ago

@annevk Promise.of isn't wrong, it's just not needed in the subset that also omits .flatMap. The difference is indeed the Promise.something(promise) === promise behavior, we do need it, and we should not leave it to libraries, because .then-level programmers, in a full AP2 system will use Promise.of where they should be using Promise.something. This is exactly the issue you reminded me that I'd reversed myself on.

The reason is that the layers of wrapping that accumulate, if Promise.something(promise) !== promise, accumulate once per iteration on many natural patterns of tail-recursive async loop, such as the old reference implementation of Q.async at the bottom of http://wiki.ecmascript.org/doku.php?id=strawman:async_functions#reference_implementation . (This needs to be updated for the new ES6 generator API.)

annevk commented 10 years ago

Looking through synonyms. Promise.like() maybe... Promise.from() seems fine though. We could just point out that unlike Array it does sometime return its argument. Or modify Array to do the same.

kriskowal commented 10 years ago

I could live with Promise.from(promise) and live with the minor inconsistency with Array.from(array). Array.from should make a copy because changes to the result have the observable side-effect of corresponding changes to the input. With promises, there should be no observable difference. (To that end, we will need to keep a side-table of thenables that have been coerced so we can consistently return the same real-promise for a given thenable and avoid attempting and possibly failing to restart a lazy thenable promise)

erights commented 10 years ago

@kriskowal I buy this as a good enough excuse for keeping Promise.from and not advocating that Array.from be changed from the current always copy behavior to the less useful conditional copy behavior. Any dissent to declaring Promise.from as the winner? If not, we should take this back to es-discuss.

domenic commented 10 years ago

Implemented as Promise.from, opening #12 to track consensus on that name.

I personally think Array.from behaving the same would be useful for its typical use cases, for the same reason of allocation being costly. The always-create-fresh use case can be served by [...maybeArrayMaybeNot], but Array.from would normally be used to ensure that what you get is an array before doing things to it. I guess those things could be mutation-ey things, but normally they're going to be filter, map, etc.


Also, not to rock the mini-consensus we've got going on here, but we could also do Promise.when(x) as the name, if that doesn't carry too much historical baggage :). Just throwing it out there!

ForbesLindesay commented 10 years ago

I've never liked when. It reads really strangely if it's not immediately followed by a .then

juandopazo commented 10 years ago

Promise(foo)++. I'm not sure from makes sense if Promise.from(promise) === promise.

BrendanEich commented 10 years ago

Domenic Denicola wrote:

Implemented as |Promise.from|, opening #12 https://github.com/domenic/promises-unwrapping/issues/12 to track consensus on that name.

I personally think |Array.from| behaving the same would be useful for its typical use cases, for the same reason of allocation being costly. The always-create-fresh use case can be served by |[...maybeArrayMaybeNot]|, but |Array.from| would normally be used to ensure that what you get is an array before doing things to it. I guess those things could be mutation-ey things, but normally they're going to be |filter|, |map|, etc.

We shouldn't go in circles. Array.from exists to support polyfillability, which [...arrayish] does not. It must make a copy, always, because arrays are mutable and returning the argument sometimes but not others creates pigeon-hole problem bugs. Finally, "from" does not connote a no-op as a preposition -- it suggests an implicit verb, "create" or "transfer" or "reconstruct" before the "from".

What to call the idempotent project-as-promise function is a good question, but I don't think it is Promise.from.

/be

BrendanEich commented 10 years ago

Brendan Eich wrote:

What to call the idempotent project-as-promise function is a good question, but I don't think it is Promise.from.

Mark urged me to suggest a name:

Promise.cast(x)

/be

erights commented 10 years ago

Summary of my reactions at this point:

kriskowal commented 10 years ago

I can live with cast.

domenic commented 10 years ago

cast is OK. I'd like to reiterate @kriskowal's point from earlier though, that Promise.from(promise) returning promise is more OK than Array.from(array) returning array because even if it did return a new promise, that new promise would be indistinguishable from the original except by ===.

BrendanEich commented 10 years ago

Domenic Denicola wrote:

|cast| is OK. I'd like to reiterate @kriskowal https://github.com/kriskowal's point from earlier though, that |Promise.from(promise)| returning |promise| is more OK than |Array.from(array)| returning |array| because even if it did return a new promise, that new promise would be indistinguishable from the original except by |===|.

I hear you on that one. We could use Promise.from and mostly get away with it. But the connotation problem I mentioned still exists, and some foolish consistency deduction still hurts. Neither of those problems afflicts Promise.cast.

/be

domenic commented 10 years ago

That's fair.

On the other hand, can we use Kris's "it doesn't matter very much" argument to make Promise.resolve not always return a new promise? I am not sure how I feel about that. But, it is notably what Q.resolve and RSVP.resolve already do.

erights commented 10 years ago

@domenic I don't understand your question. I just looked at https://github.com/kriskowal/q/wiki/API-Reference and I don't see any Q.resolve. Do you mean the call behavior of Q called as a function?

domenic commented 10 years ago

Right, we alias Q.resolve to Q; in the Q 0.8 timeframe Q was not a function and that's what we called it. We left it in Q 0.9 and haven't deprecated it officially (i.e. using it doesn't cause deprecation warnings).

erights commented 10 years ago

@domenic Ok, with that clarified, I still don't understand the question. Could you try to restate in other words? Thanks.

domenic commented 10 years ago

Sure.

Tab's original objection to Promise.resolve(truePromise) === truePromise was that the static methods should be treated as factories, and Promise.resolve(x) should desugar obviously to new Promise(resolve => x). I find this argument compelling, until I consider that a new promise resolved to an existing promise behaves exactly the same as the original promise, as Kris pointed out, modulo ===. Combined with the fact that Q.resolve(qPromise) === qPromise and RSVP.resolve(rsvpPromise) === rsvpPromise, I am asking that we reconsider letting Promise.resolve(promise) === promise.

erights commented 10 years ago

Perhaps I'm not getting it because I never found that argument compelling. IIUC, you agree that

Promise.someNameWeNeedToChoose(promise) === promise

must be true. You are suggesting we consider "resolve" for that name. Is this right?

If so, I prefer .cast for two reasons:

domenic commented 10 years ago

OK, that's fair. Dropping it :).

erights commented 10 years ago

Ok, any objections to closing this local bikeshed with .cast and taking the issue back to es-discuss?

annevk commented 10 years ago

Let's do that.