-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
[BREAKING] Remove IteratorSequence. Do not attempt to detect iterators. #1589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Out of curiosity, what is the speed/perf impact of this change? |
There could be a [very] small win since sequences are sometimes constructed internally as part of other implementations, but my motivation for this change is correctness not perf. |
This is the right thing to do. Thank you for your investigation and work. All built in iterables define |
I've relied on I still need to support an ES5 runtime environment. |
Unfortunately yes, you'll need to ensure whatever iterators provided are also Iterable. Immutable determines that via finding a function at Symbol.iterator. While you may need to polyfill that, note that Symbol.iterator doesn't actually have to be a symbol, so you can still target ES5 environments. |
@ShawnTalbert The simplest fix is this: const iteratorSymbol = require('core-js-pure/es/symbol/iterator');
Seq({ [iteratorSymbol]: () => yourIterator }); Sorry for the inconvenience to you, but from our perspective it's much safer this way. |
That polyfill isn't free but it doesn't change the global namespace which helps if you don't own the environment, and it won't introduce any more bloat than is absolutely necessary when you use a tree-shaking bundler like rollup or webpack. |
This should be mentioned in the changelog.md, I guess? |
Thanks folks, I'll try the ideas above. In my case it's end users that call |
@ShawnTalbert That sounds like a use case I'd be interested to hear about. In addition to being tangentially involved in this project I maintain iter-tools, so I'm always thinking about these sorts of things. Our decision takes a cue from the way the language itself does things: it doesn't define any iterators that are not also iterables. What it does is: const someIterator = {
next(){
/*...*/
},
[Symbol.iterator]() { return this };
}; If you can do that where the iterators are defined then you'll no longer have changes to make at every |
@ShawnTalbert Hey sorry I'm behind on this as I haven't poked into the code in a while. We're leading you on a little here -- you don't actually need to define So putting it all together, you want to change the definition of your iterators to: const someIterator = {
next: function() {
/*...*/
},
"@@iterator": function () { return this };
}; Hope this helps! |
Heh, thanks @conartist6 - indeed just a few minutes ago I ended up adding the same to my iterator class and Immutable4 seems happy. /**
* Fake Symbol.iterator to make this an `Iterable` to satisfy ImmutableJS 4.0.0 requirements on Seq()
* Since we still need NFT to work in ES5 (SS2.0) using `@@iterator` rather than `Symbol.iterator` (which does
* not exist in ES5).
*/
['@@iterator'](){ return this } |
Actually, there perhaps remains a [TypeScript] type issue? Here are all the overloads for Seq in the .d.ts file: function Seq<S extends Seq<unknown, unknown>>(seq: S): S;
function Seq<K, V>(collection: Collection.Keyed<K, V>): Seq.Keyed<K, V>;
function Seq<T>(collection: Collection.Set<T>): Seq.Set<T>;
function Seq<T>(
collection: Collection.Indexed<T> | Iterable<T> | ArrayLike<T>
): Seq.Indexed<T>;
function Seq<V>(obj: { [key: string]: V }): Seq.Keyed<string, V>;
function Seq<K = unknown, V = unknown>(): Seq<K, V>; While this seemed to work at runtime, my 'fake' ES5 |
Oof. You might be the first person to try that. Why not do something evil? You were able to define that |
I realize this is asking for support on a "legacy" pre-Symbol version of JS but if we're still (thankfully) trying to support ES5 for For example, even if evil casting is possible, it means adding some sort of Would it be acceptable to add an type IterableLike<T> = { ['@@iterator']() : Iterator<T> } I wasn't able to get any casting approaches to work (per your previous comment) but will keep trying in the meantime. Adding a type like above to Seq() would certainly make me feel better as a user of the lib. |
Your syntax is a little off there. I couldn't say how well it would work or how much work it would be without trying. You'd be the ideal person to try, since you have the real test case. If you can modify the typedefs such that they work correctly for you (and don't break things for anyone else), I'd imagine we could get the changes merged. |
The easiest way to do that in my experience is just to experiment with changing the real installed typedefs in |
…property named `@@iterator` (e.g. for environments pre-ES2015 where `Symbol.iterator` does not natively exist). Related discussion on pull request immutable-js#1589
This is actually a benefit of Typescript exposing these native feature types as separate importable libraries and why Immutable doesn't directly import the ES2015.Symbol or ES2015.Iterable libraries. You can import or write your own. The ES2015 version Typescript provides is also not all that complicated, take a look https://github.com/microsoft/TypeScript/blob/main/lib/lib.es2015.iterable.d.ts Here's the most relevant parts: /// <reference lib="es2015.symbol" />
interface SymbolConstructor {
/**
* A method that returns the default iterator for an object. Called by the semantics of the
* for-of statement.
*/
readonly iterator: unique symbol;
}
interface IteratorYieldResult<TYield> {
done?: false;
value: TYield;
}
interface IteratorReturnResult<TReturn> {
done: true;
value: TReturn;
}
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
interface Iterator<T, TReturn = any, TNext = undefined> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext]): IteratorResult<T, TReturn>;
return?(value?: TReturn): IteratorResult<T, TReturn>;
throw?(e?: any): IteratorResult<T, TReturn>;
}
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
interface IterableIterator<T> extends Iterator<T> {
[Symbol.iterator](): IterableIterator<T>;
}
interface Array<T> {
/** Iterator */
[Symbol.iterator](): IterableIterator<T>;
}
interface ReadonlyArray<T> {
/** Iterator of values in the array. */
[Symbol.iterator](): IterableIterator<T>;
} Since you only care about providing values and not consuming them, and Immutable only cares about the yield values you'd need even less for your own version for ES5 faux iterator support: interface Iterator<T> {
next(): { done: boolean, value: T };
}
interface Iterable<T> {
['@@iterable'](): Iterator<T>;
}
interface IterableIterator<T> extends Iterator<T> {
['@@iterable'](): IterableIterator<T>;
}
interface Array<T> {
['@@iterable'](): IterableIterator<T>;
}
interface ReadonlyArray<T> {
['@@iterable'](): IterableIterator<T>;
} |
I commented more on the other PR, but the specific declarations above are an ImmutabjeJS-specific definition. It's not 'my' version of a faux iterator, but rather ImmutableJS's required definition to be a faux iterator. Like any other types defined by a library, I'd typically expect the declarations to come from the library. What if Immutable decides to change these faux iterator definitions down the road? I would expect a compile-time error via updated definitions coming from that newer version of the lib. |
Fixes #1456