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

Number.range(to) equals Number.range(0, to)

littledan opened this issue · comments

I saw this raised as a question in the README, but couldn't find an issue about it.

Personally, I like the idea of supporting the Number.range(to) form. It's common in other languages and will be common in JS; I think it's quite unambiguous.

(Related discussion: #14)

commented

I think we should require both arguments. Being explicit seems strictly better to me.

Python works like this, and its range is one of the most common operations in the language and this point does not seem to cause confusion. If we want to do something different I think there should need to be a very, very good reason; otherwise we should just do what they do.

I also like range(to). But as long as we don't do the implicit Infinity upper bound in #14, I don't care. I have never actually wanted to count to infinity…

I have never actually wanted to count to infinity…

As a child, I wanted to but was never able to. I just never had the patience.

commented

We have an issue that says Number.range(to) should be a thing and an issue that says Number.range(from) should be a thing. Some group of humans are going to end up being continuously wrong in their assumptions if we choose one, so I think the reasonable conclusion is that we require the start and the end.

It is generally impossible to design APIs such that no group of humans will ever end up being confused. The fact that Python has been using these semantics for many years without many people tripping over this shows that not all that many people will be confused, I think rather conclusively.

commented

Impossible yes, but we can at least try not to make it worse, right?

I also like range(to). But as long as we don't do the implicit Infinity upper bound in #14, I don't care. I have never actually wanted to count to infinity…

One of the possible use case of that is you use the infinity sequence as a source of real numbers and you filter some out to generate another sequence of infinity numbers

I do not think that copying the semantics of one of the most widely-used operations in one of the most widely-used languages, which causes very little evident confusion there, will make it perceptibly worse.

That seems to assume that the sensibilities and intuition of Python users will overlap with that of JS users, which I don't think is necessarily true.

commented

I would say that python has done a great many things right and a great many things wrong (much like we have), and that arguing about which things go in which category is probably not a great way to make our decisions. If there are concrete usability concerns about requiring both arguments I would be more interested in discussing those instead.

The up-to range is well established even before Python: https://leancrew.com/all-this/2019/09/making-lists-with-jot-and-seq/.

I don't suspect that Pythonistas and JSers have a substantially different intuition, as a class, for whether a single-argument range counts to that number from 0 or from that number to infinity. ^_^ In particular, both languages have the same very common use-case of "enumerate all the indexes of a list of length N" which is done by "count to", whereas I've rarely encountered situations where I needed an infinitely-incrementing range. (Not never; I know of one spot in my code where I'm doing exactly that, but I'm using itertools.count() for it, and reaching for itertools for an infinite iterator felt like the intuitively right choice vs the base built-in.)

I'll say that I've used the Python range syntax without problem before, and my personal range() function in JS works this way as well. I also suspect that it's safer/better, given a reasonably even choice between creating a finite and infinite iterator, to choose the finite one, and require an author to be more explicit about wanting an infinite iterator.

commented

We could also just require them to be explicit about both. I really don't understand why we want to make guesses about what people are trying to do.

I'm not trying to argue about whether Python's design is good or bad. I'm making a more specific argument: the usual reason to prefer being more verbose is to avoid confusing people, but the millions of Python users of the less verbose design who are not confused by it is very good evidence that it is not going to confuse many people.

If there is some other reason to prefer to be more verbose, great, we should talk about that. I'm not saying Python's design is necessarily good. But I do think it means we can safely say that the less verbose design is not going to be unduly confusing.

(Also, precedent from other languages has weight on its own, in proportion to how common those languages and the portions of them which we're duplicating are.)

Edit to add:

We could also just require them to be explicit about both. I really don't understand why we want to make guesses about what people are trying to do.

We could further require them to be explicit about step = 1, so as to avoid making guesses about their intent there. Do you think we should do so? The reason not to is the same as the reason not to require from = 0 - there's a very common case which is thereby made simpler without introducing much complexity to the API.

commented

That multiple meanings for the single-arg form have been requested seems motivating enough to me. Even if we're talking about a minority of developers being confused, the majority isn't hurt by making their code more explicit too (In fact I'd argue they're helped).

We could further require them to be explicit about step = 1, so as to avoid making guesses about their intent there. Do you think we should do so?

Maybe. I always put the radix in for Number.parseInt, even when I'm parsing in base 10

I have never actually wanted to count to infinity…

As a child, I wanted to but was never able to. I just never had the patience.

I successfully did it by using induction.

Anyway, I don't feel strongly about whatever we may come up with here, but I'm leaning towards @bakkot's position.

I think trying to make arguments from a point of intuition is likely fraught, for reasons stated by folks on both sides of this argument. I think the question we should be asking ourselves is which use case is more common? Between Number.range(to) and Number.range(from), it's clearly the former. @devsnek's argument about explicitness is well-taken, and so I think the question we should ask ourselves there, is will this use case be common? That is, will it be common that folks will want to drop the leading (0, ? I think the answer there, too, is yes. Therefore I'm in favor of enabling Number.range(to). I'd be fine with enforcing both arguments, but would be against Number.range(from).

Another thought, if we choose range(to) and developers write for x of range(from), they will receive a wrong range
if we choose range(from) and developers write for x of range(to), they will have to force stop the JS engine because the dead loop.

commented

If both are required they'll get an error.

I would prefer this proposal rather than range(from) or range(from, to).

  • It is common to generate a finite size range. And generating an infinite size range would be much harder to see.
  • It is common to write for (let i = 0, len = a.length; i < len; i++) here and there. It would be the same as for (let i of range(a.length))
  • We looping from 0 to len - 1 everyday, using range(to) do save bytes and make life easier
  • You may read range(to) as there will be to numbers in the range, or construct a range with to numbers.
  • Some other languages follow this pattern. So make programmers switching between languages less more confusing.

using range(to) do save bytes and make life easier

Umm actually you have to write Number.range so not too much bytes are reduced

Personally I prefer explicitness. IMO saving 0, do not bring enough benefit. If we really want save characters, we should consider literal like 0:n.

And do we have precedents of overloading f(a, b) and f(b) which a and b are same type?

commented

Aside: is there any issue or separate proposal that discusses the possibility of a range literal over a function?

Aside: is there any issue or separate proposal that discusses the possibility of a range literal over a function?

Yes, #20 but I'd like to avoid to add new Syntax for it

One of the possible use case of that is you use the infinity sequence as a source of real numbers and you filter some out to generate another sequence of infinity numbers

Neither Infinity nor -Infinty work well as values for the to parameter. A more appropriate choice might be one of

const ascending = Number.range(from, Number.MAX_SAFE_INTEGER + 1);
const descending = Number.range(from, -(Number.MAX_SAFE_INTEGER + 1), -1);

This ensures integer ranges end before yielding duplicate values. In the case of small from values, this is effectively infinite as it would take many days of CPU time to exhaust the range.

There is no obviously correct default value for the to parameter, so I think Number.range(from) is less useful than the alternatives. In my implementation both from and to are non-optional, which has worked well in practise.

  • It is common to write for (let i = 0, len = a.length; i < len; i++) here and there. It would be the same as for (let i of range(a.length))

This proposal should not be motivated by array key iteration, the appropriate idiom is

for (let index of array.keys())

Neither Infinity nor -Infinty work well as values for the to parameter.

Um, It does work in the BigInt version. BigInt.range(0, Infinity).

IMO most use case of the infinity range won't really reach the Number.MAX_SAFE_INTEGER + 1 so it is convenient to use Infinity as the target.

Yeah nobody's actually iterating up to MAX_SAFE_INTEGER; using Infinity as a to value is just a shorthand for "i need some ascending numbers for a bit, and don't want to figure out how many I'll end up needing before I break this loop"

I propose that omitting both arguments default to (0, bound).

nobody's actually iterating up to MAX_SAFE_INTEGER; using Infinity as a to value is just a shorthand

But it still needs to be specified and something like iteration from Number.MAX_SAFE_INTEGER - 5 added to the test suite. Using Number.MAX_SAFE_INTEGER + 1 for Number and Infinity for BigInt (with corresponding negative ones) as the bound seems cleaner than alternatives.

commented

I'd prefer explicit methods like Number.from(5) == Number.range(5,Infinity), and Number.upto(5) == Number.range(0, 5)

I'd prefer explicit methods like Number.from(5) == Number.range(5,Infinity), and Number.upto(5) == Number.range(0, 5)

If we go with that, maybe we need to rename it to NumberRange (Range will conflict with web API)

I'll close this issue for now for housekeeping. On today's TC39 meeting, delegates are generally satisfied with the status quo API. It's still possible to add this shortcut in the future if there is a very strong need.