-
Notifications
You must be signed in to change notification settings - Fork 34
Iterator.from should guarantee a branded object, and helpers should brand-check the receiver #224
Comments
i thought we came to the conclusion that this was not meaningful...
because this is already true?
why? |
Can you describe what you're asking for in terms of observable behavior of code, rather than in terms of spec-internal concepts like brands? |
Sure. const iteratorLikes = [
{ next() {} },
{ __proto__: Iterator.prototype, next() {} },
{ __proto__: Iterator.prototype },
];
iteratorLikes.forEach(x => Iterator.prototype.map.call(x, x => x)); // each of these should throw
iteratorLikes.map(x => Iterator.from(x)).forEach(x => Iterator.prototype.map.call(x, x => x)); // these should all not throw |
Why? |
Let me give the argument against those throwing: right now it would be entirely reasonable to have code which looks like |
Why would that be strange? That's how most builtins behave - merely making an object with the same prototype chain simply doesn't ever guarantee you can use all the methods on it, because internal slots exist. |
It would be strange because right now there is no brand to check, so there is nothing to discourage writing the above snippet. There is currently exactly one method on Iterator.prototype, and it doesn't brand check, so there's nothing to suggest the above code is bad. In any case, why should it throw? |
It's already true, but "brand" in this case is just the iterators protocol and
Definitely not. Iterators are protocol - and the first 2 satisfy the requirements of required parts of the protocol.
It's the same protocol like array-like. It's the same like Array.prototype.map.call({ length: 1, 0: 1 }, x => x) |
Also, just see this a quite popular use case. |
Can you provide some examples of how it's popular? |
At least - all polyfills of iterators, transpiled generators, etc. Without setting a brand as you want (that's impossible in engines without proper subclassing support) those helpers will not work with them. This is just the closest use case to me. |
Well sure, any engine that needs iterators polyfilled wouldn't have iterator helpers either - polyfills aren't relevant here, since an Iterator Helpers polyfill would be able to handle the difference. Any use cases that aren't polyfills? |
I'm still extremely confused on what the use case is for the helper methods throwing. Can someone elaborate? |
@ljharb sorry, but it's full bullshit. You propose that |
All cases of iterator usage support generic iterators that can be created via protocol in the userland - all syntax features like |
Also, why did you ignore all comments about the protocol? |
@devsnek it seems for unknown for me reason he thinks that all iterators should have an internal private slot that can be created only in built-in iterators and iterators created in the userland via protocol, something like |
Please remember we have a Code of Conduct. |
It ensures that only proper iterators that follow the semantics we expect can be used with the helper methods, just like how |
A very bad comparison. Here is much closer Object.setPrototypeOf(x, Promise.prototype); x.catch(fn); x.finally(fn); that works and should work. |
And it works for the same reason why should work on methods from this proposal - a protocol, "thenable" in this case. |
I am confused about what you mean by "proper iterators that follow the semantics we expect". Can you say that more precisely, i.e. in terms of what observable behaviors are allowed or ruled out for "proper iterators", and how having
I mean, that "doesn't work" because This is exactly like how (I don't think this is always a good idea, because not every class needs to be a protocol - Promise shouldn't have been, but it is - but Iterator is already a protocol; that ship has sailed.) |
This is the part I don't understand. We explicitly want these methods to work on iterators, which have a pretty clear definition. Are you asking to change the definition of iterators? Are you saying the methods should only work on a subset of iterators? Something else? To what end? |
This is another reason we need to use internal slot instead of |
No, we don't need it. I even don't want to argue why since you just ignored all previous arguments. |
Instances of " |
Polyfilling concerns never have or will dictate how proposals operate, so I'm not sure why that keeps getting brought up. |
Polyfilling is just one example of a changing environment. |
This comment was marked as off-topic.
This comment was marked as off-topic.
@ljharb All proposals are evaluated in terms of web compatibility and developer experience. Why do you think polyfills should be ignored? |
I’m not a committee member or anyone special, but as a regular old developer who’s really excited to see these helpers land, I’m a little confused by why the helpers need to brand-check like this. I can see value in I’m not super worried about the polyfilling case (the argument that an environment that needs polyfills won’t have anything else that’s doing the brand-checking makes sense, even if it means no polyfill will be able to match the expected behavior exactly, and some code may break when moving from a polyfill to the real thing, which is obviously not great). But it seems like there’s plenty of value to be had in supporting other interesting use-cases people might come up with. Like @zloirock’s
I don’t think I would have expected that to work — why would I think the built-in // Not actually a promise, but meets the protocol requirements.
const notAPromise = {
then (onResolve, onReject) {
setTimeout(() => onResolve(5), 100);
}
};
const result = await notAPromise; (This involves the same sort of “you need to implement the protocol methods” contract iterators do and involves no special branding. It seems much more analogous to what’s being talked about here to me. I tried this out in Node.js, Safari, and Firefox, but will admit I have not read over the spec to see if they aren’t all technically misbehaving.) |
@bergus absolutely the web compat of existing polyfills matters a ton - more than of most code. However, if polyfillability were a workable concern, no new syntax would land, and a bunch of existing proposals wouldn't have been able to land as-is. The committee has explicitly decided many times that while polyfills are important to consider, polyfill ability is not. |
The new syntax is about transpilers. It seems transpilability is a concern since almost all syntax is transpilable. Polyfills about built-ins. Adding a brand check here will make ALL iterators unpolifillable in the future - I already added an example: Number.range(0, 10).map(fn); will not work since polyfilled Sure, it can be worked around by polyfilling methods from this proposal everywhere - even after 10, 20, 30... years - and it will increase the size of bundles of almost all websites that use polyfills to some dozen of KB forever. But since a brand check here is not something really required - it's full bullshit. If the concern about polyfillability where it's possible and not required will not be revised - it will be required to take some actions. |
It is not clear what kind of concrete solution we could have here. Iterators are not — and will not be — branded. |
"will not be" is a pretty absolute statement to be making on a proposal you hope to advance :-/ |
Rather making branded objects from iterators looks like a proposal. Destructive and absolutely unrelated to the current proposal. |
@ljharb Sorry, maybe "cannot be" would have been better wording? We do not control where iterators appear. They can just be created anywhere by anyone without a brand and without our blessing. |
@michaelficarra absolutely. but we can force those to be passed through |
@ljharb I don't know what you mean by "just like we do with Promises". |
And all the ways that it still works "fine" even if the |
No, it isn't. |
It's not? |
let mySpecialThing = {};
let fakePromise = {
__proto__: Promise.prototype,
then(){ return mySpecialThing; }
};
fakePromise.finally(()=>{}) === mySpecialThing // true |
Missed opportunity. None the less, |
I am still not clear on the property you're talking about. Can you describe it in terms of the observable behavior for |
Concretely: because iterators do not have internal state visible to the spec, the only possible behavior for Above you suggested that This hypothetical brand check would add complexity, and makes otherwise functional code throw, so there needs to be some reason for it. As we've just discussed, the brand check would make these helper methods unlike So: what advantage does the additional brand check have? Above you said "it ensures that only proper iterators that follow the semantics we expect can be used with the helper methods", but I have no idea what "proper iterators that follow the semantics we expect" means or how this brand would help verify that. Maybe you can write that out? What specific behavior constitutes a "proper iterator", and how does the proposed brand check ensure that behavior is followed? |
Additionally, iterators produced by a generator, or by any of the builtin iterators, should not need wrapping.
This allows
Iterator.from
to be reliably used likePromise.resolve
- given any user-provided alleged iterator-like object, I can pass it throughIterator.from
and I'll have a properly-functioning "real" iterator.The text was updated successfully, but these errors were encountered: