tc39 / proposal-iterator.range

A proposal for ECMAScript to add a built-in Iterator.range()
https://tc39.es/proposal-iterator.range/
MIT License
487 stars 13 forks source link

Inclusive range #26

Closed hax closed 4 years ago

hax commented 4 years ago

Current proposal only cover exclusive range, I'm wondering whether we could add inclusive range support by overloading the third parameter:

Number.range(1, 5) // 1, 2, 3, 4
Number.range(1, 5, 2) // 1, 3
Number.range(1, 5, {inclusive: true})  // 1, 2, 3, 4, 5
Number.range(1, 5, {step: 2, inclusive: true}) // 1, 3, 5

Another possibility is using a separate method:

Number.inclusiveRange(1, 5)  // 1, 2, 3, 4, 5
Number.inclusiveRange(1, 5, 2) // 1, 3, 5

Possible range literal (coming from slice notation, note I'm not a big fan of literal for ranges, just throw the idea):

(1:5) // Number.range(1, 5)
(1~5) // Number.range(1, 5, {inclusive: true})
(1:5:2) // Number.range(1, 5, 2)
(1~5:2) // Number.range(1, 5, {step:2, inclusive: true})

Note (1:5:2) and (1~5:2) is not symmetry, so maybe we could change them to (1:5; 2) and (1~5; 2)

michaelficarra commented 4 years ago

Can't you build this very easily by just appending the end value onto the result of Number.range?

ljharb commented 4 years ago

In particular, if the iterator helpers proposal (or a follow-on) added a "concat" operation, it'd be trivial to do something like Number.range(x, y).concat([y]) - otherwise i think you'd need a generator like function* range(x, y) { yield* Number.range(x, y); yield y; }, which isn't particularly ergonomic.

Jack-Works commented 4 years ago

Or range(x, y + step, step) to get a inclusive range

hax commented 4 years ago

Yeah, it's just about ergonomic. I believe that's why many other languages (ruby, groovy, swift, kotlin, etc.) support both exclusive/inclusive ranges. And for those not have built-in inclusive range, there are always stackoverflow questions (try search "python inclusive range" 😂)

thecodejack commented 4 years ago

but isn't it weird that only one of the boundaries is getting added to the range?

This is how I see it working at present.

[...Number.range(2,5)]
 [3, 4, 5]

I mean why not 2 if we are adding up 5?

default could have been full inclusive.

ljharb commented 4 years ago

I would expect [2, 3, 4].

hax commented 4 years ago

@thecodejack For range(2, 5), I think no one expect 3, 4, 5. The possible expectations are 2,3,4,5 (inclusive range) or 2,3,4 (exclusive range). Some languages provide both (with different syntax), some only provide exclusive range (python).

ljharb commented 4 years ago

Which one matches .substring?

Jack-Works commented 4 years ago

I'm sorry the current spec (and polyfill) have two bugs. I accidentally inverted the inclusive and exclusive on the start and end. Please checkout #27

@thecodejack

littledan commented 4 years ago

An inclusive range feels like overkill to me, when you can adjust the end value instead.

kmiller68 commented 4 years ago

I would personally be very surprised if I ran the following code and didn't see the follow logs:

let str = "abc"
for (let i of Number.range(0, str.length))
    console.log(str[i]);
// logs "a", "b", "c"
tabatkins commented 4 years ago

Yeah, lots of patterns lead you to the inclusive-exclusive range; anything else would make a lot of things bad. range(0,5) absolutely has to be [0,1,2,3,4].

leobalter commented 4 years ago

I would personally be very surprised if I ran the following code and didn't see the follow logs:

let str = "abc"
for (let i of Number.range(0, str.length))
    console.log(str[i]);
// logs "a", "b", "c"

@kmiller68 I agree with this. IMO, working with the length property is the only reason that makes me not want a fully inclusive range method. I don't want to force a o.length - 1 case.

An inclusive range feels like overkill to me, when you can adjust the end value instead.

@littledan can you extend why is it overkill? I can't see this way and I'd like to understand it a bit more.

Yeah, lots of patterns lead you to the inclusive-exclusive range; anything else would make a lot of things bad. range(0,5) absolutely has to be [0,1,2,3,4].

@tabatkins My challenge is how do we name the second parameter?

The current proposal says:

Number.range( from , to, step )

I read the parameters as:

It's hard for me to describe that we start from a number and set the range until the number on the second parameter.

We should probably draft a better signature for this function.

leobalter commented 4 years ago

btw, Lodash's range is inclusive-exclusive.

I believe it's a good model to follow, unless we have a very strong to make things differently.

Number.range(1, 4) returning [1, 2, 3]

littledan commented 4 years ago

I think everyone's agreeing with the spec text and polyfill that, by default, we should be inclusive of the lower end and exclusive of the higher end, as @leobalter and @kmiller68 are suggesting. See https://github.com/tc39/proposal-Number.range/pull/27 for the bug fix confirming this intention. The issue is about whether we should add an option to be inclusive of both ends. I don't understand under what situations you'd need this setting to be inclusive of both ends, given that you can just add 1 (or the step value) to the end to get the inclusive-inclusive semantics.

hax commented 4 years ago

Thank you @littledan to clarify the issue.

I'm sorry that I use "exclusive range" which really mean "inclusive-exclusive", and "inclusive range" which really mean "inclusive-inclusive", which may cause confusion. I use these terms as it seems how python guys use.

I don't understand under what situations you'd need this setting to be inclusive of both ends

Personally I'm ok with only exclusive range. But I notice that there are always people expect inclusive-inclusive ranges and many other programming languages provide both (and at least Kotlin only provide inclusive range syntax a..b, and exclusive range a until b seems secondary). I guess it's mainly for completeness and ergonomic.

hax commented 4 years ago

For example, Ruby range provide both (inclusive and exclusive), and, the signature of Range.new is new(begin, end, exclude_end=false), aka. Range.new(1, 10) create an inclusive range by default 😆

thecodejack commented 4 years ago

@Jack-Works

Thanks for fixing it.

[...Number.range(2,5)]
 [2,3,4]

is good for me

Jack-Works commented 4 years ago

Implemented in #32