C. Scott Ananian (2015-04-30T17:57:16.000Z)
d at domenic.me (2015-05-11T16:52:00.320Z)
On Wed, Apr 29, 2015 at 6:32 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: > That can't right. x could be an object that is not a C but whose > SpeciesConstructor is C. In that case, a new C promise on x still has to > be created. But your code would just return x. I would use a 'construtor' > test in step 2. But if 'constructor' is not C and SpeciesConstructor is > also not C then an extra level of C wrapping is needed. Let's call the Brendan proposal above the "early test" proposal. It seems to me that if `P.@@species` is `Q` then `P.resolve` should always return an object `x` with `x.constructor==Q`, which I don't think any of the proposed tweaks yet do. So here's that variant, which I'll call the "late test" proposal: > Promise.resolve(x) > 1. Let C be the this value. > 2. If Type(C) is not Object, throw a TypeError exception. > 3. Let S be Get(C, @@species). > 4. ReturnIfAbrupt(S). > 5. If S is neither undefined nor null, let C be S. > 6. If IsPromise(x) is true, > a. Let constructor be the value of Get(x, "constructor"). > b. ReturnIfAbrupt(constructor) > c. If SameValue(constructor, C) is true, return x. > 7. Let promiseCapability be NewPromiseCapability(C). > 8. ReturnIfAbrupt(promiseCapability). > 9. Let resolveResult be Call(promiseCapability.[[Resolve]], undefined, «x»). > 10. ReturnIfAbrupt(resolveResult). > 11. Return promiseCapability.[[Promise]]. This moves the test down from step 2 to step 6, and ensures that we test `x.constructor` against the same species that we would use to create the capability. The returned promise thus always has a consistent value for its constructor property. It's hard for me to determine which of these is actually preferred, although both are more consistent that the current [[PromiseConstructor]] semantics. It would help if the ES2015 proposal actually had any objects where `C[Symbol.species] !== C`, but it does not appear to. So let's look at the smalltalk usage of species. To quote an arbitrarily-chosen smalltalk glossary ( http://www.mimuw.edu.pl/~sl/teaching/00_01/Delfin_EC/Glossary.htm): > *species*: The generic class of an object. This is normally the same as > the class of an object, otherwise it is a class which describes the generic > group of classes to which a particular class belongs. Note that this does > not necessarily have to be class from the same branch of the hierarchy. With this meaning for `@@species`, when one sets `MyPromise.constructor[Symbol.species] = Promise;` then one is saying that even though this is a `MyPromise` it's still generically a `Promise` (of the `Promise` species). Then the "early test" `Promise.resolve` is entirely consistent in passing through anything whose species is `Promise`. The "late test" semantics are a bit harder to describe in terms of species (even though they seem more consistent if you are just looking at the `constructor` property of the result). The smalltalk motivating example usually given for species is the equivalent of: ``` WeakArray.constructor[Symbol.species] = Array; ``` so that the result of `WeakArray.filter(...)` isn't itself a weak array and so won't mysteriously disappear in the hands of the caller. Moving this example to `Promise`s we'd have: ``` WeakPromise.constructor[Symbol.species] = Promise; ``` and `WeakPromise` would hold a weak reference to some value, but it wouldn't cause the promise returned by `Promise#then` to also be a weak reference. Moving from the reference domain to the time domain, we can imagine that: ``` TimeoutPromise.constructor[Symbol.species] = Promise; ``` could also be used to ensure that a given promise is rejected if not resolved within a certain time period, but the "timeout" functionality is not intended to be inherited by promises chained with `then`. The "early test" and "late test" proposals give different results and interpretations for: ``` p = Promise.resolve(weakPromise); p = Promise.resolve(timeoutPromise); ``` In the "early test" proposal, both the `weakPromise` and the `timeoutPromise` are both already of the `Promise` species, and so don't need to be recast. In the "late test" proposal these would both be recast to new `Promise`s -- possibly creating a new strong reference to the value in the `weakPromise` case. On the other hand, both "early test" and "late test" give awkward semantics for: ``` pp = WeakPromise.resolve(p); pp = TimeoutPromise.resolve(p); ``` Presumably these invocations are intended to ensure that `pp` holds a weak reference to the value or times out, respectively, even if `p` is already a generic `Promise` ("belongs to the Promise species"). But both "early test" and "late test" semantics potentially return generic `Promise` objects here, although the "early test" semantics do pass through a `WeakPromise`/`TimeoutPromise` if it is passed as an argument. I think what this is saying is that `Promise.resolve` is more like a constructor than an instance method, and it shouldn't consult `@@species` at all. So here's one more proposal. We'll call this the "no species" proposal: > Promise.resolve(x) > 1. Let C be the this value. > 2. If IsPromise(x) is true, > a. Let constructor be the value of Get(x, "constructor"). > b. ReturnIfAbrupt(constructor) > c. If SameValue(constructor, C) is true, return x. > 3. Let promiseCapability be NewPromiseCapability(C). > 4. ReturnIfAbrupt(promiseCapability). > 5. Let resolveResult be Call(promiseCapability.[[Resolve]], undefined, > «x»). > 6. ReturnIfAbrupt(resolveResult). > 7. Return promiseCapability.[[Promise]]. At this point I think "no species" is the correct thing to do. It also happens to be the simplest. If someone can come up with some alternative use cases for Promise.@@species I could perhaps be persuaded to change my mind.