tc39 / proposal-iterator.range

A proposal for ECMAScript to add a built-in Iterator.range()

Home Page:https://tc39.es/proposal-iterator.range/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Iterable numbers: why not?

zloirock opened this issue · comments

for (const i of 3) {
  console.log(i); // => 0, 1, 2
}

[...10]; // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Array.from(10, Math.random); // => [0.9817775336559862, 0.02720663254149258, ...]

Something like:

Number.prototype[Symbol.iterator] = function () {
  return Number.range(0, +this);
}

This feature with this semantic was implemented in early versions of core-js (to core-js@2) when core-js provided not only polyfills for standard features, it's already existent on a big percent of websites, so most likely it will not break the web.

(It did, in fact, cause breakage - just not in the web, but rather in a number of my tests which hadn't accounted for core-js 2 doing this 100% non-standard thing)

I find iterable numbers hugely unintuitive, and it provides no way to start at a number other than 0. There's also a lot of places that take iterables where it would be super weird imo to take a number - Array.from being one, but also new Set(10)/new Map(10), which people might think makes an empty collection of that size (like Array), and this change would create a collection populated with the range; Promise.all(10) is almost certainly a bug, but it'd give you a Promise for an array of that range; etc.

@ljharb sure, if you tested that numbers are not iterable - it will not pass your tests, IIRC you also tested that some builtins had a specified set of methods - but after new methods were added to them. It's not related to the web. For me, all those cases look more intuitive than Number.range(0, x), my examples above. new Map(10) will throw an error for the obvious reason.

The reason is not obvious at all to me (I realize it's because it's not an iterable of entries), and I doubt it would be obvious to most.

...Promise.all(map) is also almost certainly a bug, but it will return a promise with an array of pairs.

Sure, but Maps don't have literal syntax, so that's unlikely to ever happen by accident.

It's a strange argument, but anyway, if you wanna literal syntax - Promise.all('string') or new Set('string') - absolutely the same.

2 examples from the readme:

for (const i of BigInt.range(0n, 43n)) console.log(i) // 0n to 42n
// vs
for (const i of 43n) console.log(i) // 0n to 42n

function * even() {
  for (const i of Number.range(0, Infinity)) if (i % 2 === 0) yield i
}
// vs
function * even() {
  for (const i of Infinity) if (i % 2 === 0) yield i
}

Indeed, it would be shorter - but i find “more verbose” to be more readable, here and often.

The "more verbose" does not look "more readable" at least for me. IIRC you also against for-of loops.

I'm not sure what you think citing a five-year-old comment about a company's style guide will accomplish here.

Regardless, I agree with Jordan here - while I do iterate 0-N quite often, I'd prefer that be addressed with an explicit Range syntax, rather than special-casing integers. In particular, this would create another value that's infinitely recursively iterable, and thus must be special-cased when doing generic operations like flattening, like strings require today.

(That is, when you iterate a string, the items you get are strings, which can be iterated again to produce strings, which can be iterated again to produce strings, etc. With this proposal, integers would also be iterable, with their items being integers, which can be iterated again to produce integers, etc. I think infinitely-iterable strings are a mistake, and would prefer we not spread that mistake to more types.)

I'm not sure what you think citing a five-year-old comment about a company's style guide will accomplish here.

It's just an example of good readability ;-)

I don't see any conflict with that position (one i still hold) and this one here.

I think infinitely-iterable strings are a mistake, and would prefer we not spread that mistake to more types.)

this article really convinces me. I think we should not repeat the same error again.

commented

strong agree on disallowing anything infinitely iterable. Just last week I wrote this code

So I think we can close this for now since most of us think of this as a bad practice but still welcome continue the discussion on this topic.

I also think that making strings iterable, moreover - indexable, was an error because of code points / code units semantic, but numbers are another case.

"Infinite" case is something that should be taken into account only on flatten or something like that - but since strings already iterable, it's already taken into account and added object check almost everywhere.

If it will not be done, we will lose a chance to add to the language a maximally short and readable way to say, for example, "iterate X times" (for (const i of X)) or "generate an array of X results of fn function calling" (Array.from(X, fn)) - and it's without the creation of new syntax and definition of many new methods. Which alternatives could you propose or you think that "more verbose" always is better?

Actually, I think for (const i of 10) or [...10] looks good to me, but make it an iterable? probably not...

The argument for Infinite Descent Shouldn't Be Thrown Around Casually part and Strings are Rarely Collections (in the link above) also applies to numbers. Number's are not used as collections generally so make it iterable might be super surprising like new Set("abc") does.

Number's are not used as collections generally so make it iterable might be super surprising

Generators are not collections, they are used for many different cases, but they are iterable and it's one of the main parts of their design.

I think for (const i of X) looks pretty, but it comes with all of bad parts discussed here. It also lacks features compared to for (const i of X..Y), which would allow a lower bound (and all the features of this proposal). Given that, I don't think iterable numbers are pretty enough to be justified.

Generators are not collections, they are used for many different cases, but they are iterable and it's one of the main parts of their design

I disagree, generators are pretty analogous to collections. They're not indexable, instead you call next().

Generators are not collections, they are used for many different cases, but they are iterable and it's one of the main parts of their design.

My view is that generator functions are factories for generator objects that reify lazy evaluation of a collections of values. Unlike numbers and strings, the collection of values obtained from a generator object are not used as though they were a single primitive value.

generators are pretty analogous to collections

reify lazy evaluation of a collections of values

Or they could be used for control flow, stoppable functions, custom async functions, or as a state machine, or... There are many different use cases.

Note that I expand on the definition of "collection" in the very first paragraph of that section:

Something being a collection means that the important part of it is that it's a sequence of individual things, each of which is important to your program.

Generators absolutely fall under that definition the majority of the time; each individual item produced by the generator is, usually, a meaningful semantic unit of state in your program that you want to operate on. Strings fail that because the individual characters are usually not individually meaningful; your program usually cares only about the whole string at once (or subsets of it that are still larger than "character").

Integers don't even have that much going for them as collections; there's no smaller parts that they're meaningfully made of at all. You have to very intentionally mentally recast them into an explicit sequence before it starts even remotely seeming like a "collection" per my definition.

Sorry, I can't accept this definition. For example, a program is "a sequence of individual things, each of which is important" - but I definitely can't call it "collection".

That's stretching the definition past its breaking point for no real benefit to the argument, but sure. You're not required to accept my arguments for my own opinions. ^_^

A program is absolutely a collection of statements.

commented

this thread is becoming a collection of aaaaaa

@ljharb if it's just a program, but not, for example, AST, it's not a collection since you can't get access to elements of this collection - statements.

This convo has strayed into irrelevant semantic arguments that have no bearing on the topic or this repository in general. The issue is closed, in any case.

@tabatkins

welcome continue the discussion on this topic

This issue is closed also because of your irrelevant semantic arguments that have no bearing on the topic or this repository in general 😆

The only real argument that I see is infinite iterability in the language - but this case is already broken at least because of strings, avoiding it for numbers makes no sense since it will not fix this problem and, because of strings, object check is (most likely) present in all related cases.

In core-js at this moment present one more infinite iterability case - the previous iteration of your Math.seededPRNG proposal 😉

I got lost reading this thread, and I have no idea how I got here 😅

Racket has this feature. Convenient.

Generators are not collections, they are used for many different cases, but they are iterable and it's one of the main parts of their design.

@zloirock Actually I think make generators/iterators iterable is also a mistake. (JS inherit this mistake from python.)

Iterable normally could be consumed many times safely, but iterator/generator can only be consumed once, this especially bad for all upstream/downstream iterators would also make upstream/downstream iterators drain. It just so easy to spread the mutation states to many iterators and lose the control. So make iterator/generator iterable are error-prone. (Note Rust, D or any language use value type semantic on iterators is ok, because u get a non-consumed iterator when assign.)

mistakes is an infinitely iterable collection