Ranges

# kdex (8 years ago)

A few months ago, there was a thread on ESDiscuss [1] where somebody asked for native Range support, i.e. numeric ranges, string ranges, etc.

It seems like it didn't receive much attention, so I'd like to bring this up for discussion again.

If you were to iterate from X to Y, you would usually use a for loop that uses a loop variable i, which you would declare with let. You can't use const, since you'll have to increment it, and you don't want to use var, because it makes no sense to make a reference to i outside of the loop, unless you were to re-use the loop variable for further loops.

In other words, you're forced to choose a scope where other scopes barely make any sense. Having a for-loop that abstracts over this needless explicitness could help make the concept of iteration more declarative.

Python has range() (but no for loops that create ranges) [2], and Scala introduced two for-loop syntaxes that will create ranges for you:

for (i <- 1 to 10) { … } // 10 inclusive
for (i <- 1 until 10) { … } // 10 exclusive

There's a plethora of npm packages that implement ranges, the most popular one apparently being fill-range with 7.4 million downloads a month, so it seems that the community would appreciate native support a lot.

I've searched through some proposals, but couldn't find anyone currently writing a spec for it. Is there general interest for ranges at TC39? It struck me as odd that the only thing I found about them in ES was [1].

[1] esdiscuss.org/topic/feature-request-add-range-type-data [2] docs.python.org/3/library/functions.html#func-range [3] www.npmjs.com/package/fill

# Hikaru Nakashima (8 years ago)

How about this

for ( i of Array.range(1, 10) ) { ... }
// OR
for ( i of [1..10] )  { ... }
# Isiah Meadows (8 years ago)

I'll note, just for clarity, that Scala's 1 to 10 is technically just a normal method call equivalent to (1).to(10), with optional parentheses removed.

Also, I'd prefer this to be a generator instead, so infinite ranges are also possible, and so it doesn't have to be eager.

# kdex (8 years ago)

Agreed. There's no reason why Array.range or [1..10] couldn't just return a generator or at least something that extends a generator, though. I wonder if it's viable to implement something akin to .length on ranges, which could be natural numbers or Infinity.

As for numbers, I don't see any issues. One issue that came up in the original thread was that string ranges may need a better definition, as ["A".."C"] might not necessarily transpile to be a generator that yields "A", "B" and "C".

# Isiah Meadows (8 years ago)

If string ranges are based on character codes, it will (for the Latin alphabet, at least, not necessarily for other languages).

I would prefer a function over syntax, though, since it would be more easily adopted (polyfill > syntax), and it would fit more idiomatically

with the rest of the language (which also uses functions for most utilities).

Maybe a Number.range would work?

Number.range = function *range(start, end=undefined, step=1) {
  if (end === undefined) [start, end] = [0, start];
  if (end === undefined) end = Infinity;
  for (let i = 0; i < end; i += step) {
    yield i;
  }
};
# Michael J. Ryan (8 years ago)

If there's a Number.range, if suggest a corresponding String.range for character ranges... Agreed on it being a utility function over me syntax.

# Viktor Kronvall (8 years ago)

For String.range what would the expected result of String.range('A','zzz') be?

Is an exhaustive pattern expected?

['A','B','C',...'Z','a','b','c',...,'z','AA','AB',...] 2016年11月3日(木) 19:21 Michael J. Ryan <tracker1 at gmail.com>:

# kdex (8 years ago)

About the code points: String.range should also handle surrogate pairs, similar to for..of does it. About String.range("A", "zzz"): Do any other possibilities even make sense?

# Viktor Kronvall (8 years ago)

Actually, after giving it some more thought for that case there is just that one possibility that makes sense.

However, there are more ambiguous cases such as String.range("AAA", "ZZZ") (should all letters increase at once or should the rightmost letter be incremented first)

Also, how would range handle the arguments in inverted order? Should there be a decreasing range or should it terminate with no elements in the iterator? 2016年11月3日(木) 21:05 kdex <kdex at kdex.de>:

# Viktor Kronvall (8 years ago)

Even more interestingly what would String.range("","zzz") produce. From what code point is the range started? Will this throw? Is the empty string included in the iterator? 2016年11月3日(木) 21:18 Viktor Kronvall <viktor.kronvall at gmail.com>:

# Isiah Meadows (8 years ago)

Could we just not have a String.range?

  1. Needing a list of alphabetically sorted characters is a bit niche, even in the Latin alphabet.
  2. It's ambiguous whether it should include symbols or not. (think: String.range("A", "z"))
  3. If it's Unicode-aware, how should it act with, say, String.range("A", "π")?
# kdex (8 years ago)

I don't think we should give the Latin alphabet special treatment. This is solely about Unicode codepoints.

I'd expect…

- `String.range("AAA", "ZZZ")` to generate ["AAA", "AAB", …, "ABA", … "ZZZ"].
- `String.range("", "zzz")` to either throw or be equivalent to `String.range("\u0000", "zzz")`.
- `String.range("z", "A")` to either throw or wrap around at "\u{ffffff}" and therefore include
	all codepoints.
- `String.range("A", "z")` to include symbols, too. If you only need letters in latin-1, you should
	either write a custom latin-1 generator or skip these values with a RegExp when iterating
	over them. Or just use two ranges, one for A-Z and one for a-z.
- `String.range("A", "π")` to include all characters from "\u0065" (A) to "\u03c0" (π).
- `String.range("😀", "😂")` to generate ["😀", "😬", "😁", "😂"] (surrogate pairs being respected)

For more complicated cases, it might make more sense to make users pass a function to String.range for fine-grained control, or maybe just make users create their own generators.

# Zach Lym (8 years ago)

With regard to syntax, Rust's is the best that I've seen: '0 ... 10' inclusive and '1 .. 9' exclusive.

for ( i of [1..10] ) { ... }

Why do we need to clutter the syntax with brackets?

Strings are something that's needed, I've been tinkering with a LINQ syntax for IndexedDB and a Range type would make it easier to take advantage of indexing.

However, there is not right answer here: is "10A" > "001B"? What if I'm

storing DNS labels, where mail.example.com < *.example.com?

I think there should be a sane default (perhaps with syntactic sugar to specify region encoding and Unicode normalization) but just people to create extend a Range object so they can build their own semantics.

Thank you, -Zach Lym

# Alexander Jones (8 years ago)

+1000

Classic feature creep with basically zero application.

# Michael J. Ryan (8 years ago)

Your right... The more I think about it...

Number.range(from=0, to=Number. MAX_SAFE_INTEGER)

Is probably best, as a signature, then using .map(...) If you want a string representation...

For that matter, wouldn't mind seeing more of the array utility methods, like map, added to strings, iterating through each character, same as .split('').map, but that's another discussion.

On Nov 4, 2016 3:11 AM, "Alexander Jones" <alex at weej.com> wrote:

+1000

Classic feature creep with basically zero application.

# Jordan Harband (8 years ago)

Here you go:

  1. function* range(start, end) { for (const i = +start; i < end; ++i) { yield i; } }
  2. function range(start, end) { return Array.from({ length: start - end }, (_, i) => start + i); }
# Mathias Bynens (8 years ago)

On Fri, Nov 4, 2016 at 6:24 PM, Jordan Harband <ljharb at gmail.com> wrote:

Here you go:

  1. function* range(start, end) { for (const i = +start; i < end; ++i) { yield i; } }

For future reference: ++i throws when i is a const binding. The intended example uses let instead.

# Andrea Giammarchi (8 years ago)

FWIW: got bitten from same thing too ... the fact for(const x of ...) works would always mislead me to think a for(const i; ... i++) would too, even if it's completely different meaning/issue/matter.

Oh well

# Jordan Harband (8 years ago)

lol thanks, good call. too much habit typing "const" :-p s/const/let there

# Andy Earnshaw (8 years ago)

On Thu, 3 Nov 2016 at 22:11 Zach Lym <zachlym at indolering.com> wrote:

With regard to syntax, Rust's is the best that I've seen: '0 ... 10' inclusive and '1 .. 9' exclusive.

I love Rust's syntax too, but unfortunately 1..<something> is already valid

ES syntax:

1..Infinity
//-> undefined

Number.prototype.foo = 'bar'
1..foo
//-> 'bar'

You'd have to enforce the spaces around the operator, which would be weird IMO. ... would be ok though.

# Hikaru Nakashima (8 years ago)

My idia is as follows:

[1..5]  //-> [1,2,3,4,5]

(1..5)   //-> iterate 1, 2, 3, 4, 5


[1..Infinity]  // -> TypeError because n > 2**32-1

(1..Infinity)  // -> valid iterator
# Tab Atkins Jr. (8 years ago)

On Tue, Dec 13, 2016 at 3:07 AM, Hikaru Nakashima <oao.hikaru.oao at gmail.com> wrote:

My idia is as follows:

[1..5]  //-> [1,2,3,4,5]

(1..5)   //-> iterate 1, 2, 3, 4, 5


[1..Infinity]  // -> TypeError because n > 2**32-1

(1..Infinity)  // -> valid iterator

As Andy just explained in the previous message, that doesn't work. In particular, [1..end] is equivalent to [(1).end], which is a perfectly valid expression that creates a length-1 array containing the value of the "end" property from a Number wrapper auto-constructed around 1. (Which happens to be undefined, unless you do shenanigans.)

# Hikaru Nakashima (8 years ago)

Oh, I understood it. It looks like serious problem, but it is may not actually. If this spec change doesn't break web, we can introduce this idea?

# Andy Earnshaw (8 years ago)

I think you'd be lucky to even get to that stage. Vendors aren't keen on any kind of backwards incompatibility in new specs and trying to get this to stage 4 with such a glaring one would be practically impossible.

It's not just the incompatibility either. You also introduce an inconsistencies where things like [1..toFixed(2)] doesn't mean the same as [ 1..toFixed(2) ]. That kind of thing is just confusing to developers.

When you consider these things, it becomes clear that it's not practical to change the language this way for such a small benefit.

# Hikaru Nakashima (8 years ago)

I understand. I hope to find a good form of literals.

Is there a fact that literals are easier to optimize in the following cases?

for (let i of [1 to 5]) { ...... }
vs
for (let i of Array.range(1, 5)) { ...... }

If so, it seems that we can attract vendors' interests.

2016-12-14 17:29 GMT+09:00 Andy Earnshaw <andyearnshaw at gmail.com>:

# Jeremy Martin (8 years ago)

While slightly more verbose, the previously suggested ... syntax does have a superficial consistency with the spread operator. Both perform an expansion of sorts, which has a subtle elegance to it, IMO.

# Alexander Jones (8 years ago)

IMO this is quite unnecessary syntax sugar. Python has everything you could need here without special syntax.

# Cyril Auburtin (7 years ago)

What I'd really like is something to avoid Array.from({length: n}, (_, i) => ..)

It's very common to use it nowadays

on the + side, it's a wider feature than range, the callback is more powerful to build any kind of ranges

but it feels quite hacky and verbose. you can make a typo on 'length', and have to use the second callback argument.

I'd like a lot a Array.whateverNameAsShortAsPossible(4, i => 2*i+1)(gives [1, 3, 5, 7])

I think Array.build was proposed a long time ago

# Jerry Schulteis (6 years ago)

At least Array.build isn't "taken" by MooTools.

For the particular example, I prefer something like [...take(4, oddNumbers())], but in some cases Array.build would provide better clarity.

On Sunday, June 24, 2018, 10:34:58 AM CDT, Cyril Auburtin <cyril.auburtin at gmail.com> wrote:  

What I'd really like is something to avoid Array.from({length: n}, (_, i) => ..)It's very common to use it nowadays

on the + side, it's a wider feature than range, the callback is more powerful to build any kind of ranges but it feels quite hacky and verbose. you can make a typo on 'length', and have to use the second callback argument.

I'd like a lot a Array.whateverNameAsShortAsPossible(4, i => 2*i+1) // [1, 3, 5, 7] I think Array.build was proposed a long time ago (array.build)

# Isiah Meadows (6 years ago)

I'd love to see ranges, but only implemented as iterables. But in reality, we really should start pushing for a proposed iterutils module (or similar) that has all this widely useful stuff that doesn't really have a place in the global scope, but are still generally useful. Granted, this is currently blocked on the pipeline operator proposal IIUC (not on TC39, but I've heard/read things hinting at it), but that's the main thing that really needs to happen.


Isiah Meadows me at isiahmeadows.com, www.isiahmeadows.com

# N. Oxer (6 years ago)

I think something like itt is a good prototype/example for a possible iterutils module.

# Cyril Auburtin (6 years ago)

In any case, there really needs to be a JS core function to generate sequences/ranges

I used a lot Array.from({length: ... but it's far from ideal

related example: graphql/graphql.github.io/pull/456/files/1523f6dcf333eb869c96789d8f099c1a192c032a#diff-e1de70658aec08b7d432c434a64c637aords

I'd like at least something like seq(numRolls).map(() => this.rollOnce())

a seq global function wouldn't be less useful than a for keyword

Le mer. 27 juin 2018 à 01:07, N. Oxer <blueshuk2 at gmail.com> a écrit :

# Bob Myers (6 years ago)

It seems odd that after all these years of discussions and meta-discussions about ES feature proposals, some people are still saying things like:

  • there really needs to be
  • I'd really like
  • I'd love to have

often without addressing a single one of the relevant questions:

  1. Is it sugar? Is it "mere" syntactic sugar (which is not disqualifying in and of itself), or something that requires (or benefits from) being baked into the language?
  2. How much sugar? If it is wholly or partially syntactic sugar, what the degree of syntactic optimization?
  3. Frequency of benefit? What is the frequency of the use case?
  4. Expected improvement? If it is something that would benefit from being baked into the language, what is the degree of the benefit (eg, in terms of performance)?
  5. Userland implementable? Can it be implemented in userland code? If so, what's the downside of that?
  6. Implementable? Does it present potentially difficult or intractable implementation challenges?
  7. Consistent? Is it consistent with existing syntactic and semantic practices in the languages?
  8. Holistic? Does it fill in some obvious logical gap in the current language design?
  9. Understandable? Does it place an unsustainable new "cognitive burden" on learners and users of the language?
  10. Library? Is is something that would be better provided as part of some kind of future standard library?
  11. Intrusive? Does it take over real estate that might be useful for future features no one has thought of yet, the obvious example being using special characters?
  12. Readability? Is it something that results in a distinct improvement in readability or visible semantic correctness of code?
  13. Prior art? Has this or a similar feature already been proposed, and if so what was the reaction, and how is your proposal different from that, or from a similar features existing in other languages?

I'm sure there are cases where simply throwing out an informal idea and seeing how people react is useful to get a discussions started, but most reactions will be that the proposal does not meet one or more of the above criteria, so proposers could save themselves and other people lots of time in advance by explaining HOW their proposal satisfies these points, not all of which are relevant to every proposal, but those which are.

Bob

# Cyril Auburtin (6 years ago)

Sure, good points

  1. Is it sugar? Yes

  2. How much sugar? Very few, example: (a, b, s=1)=>Array.from({length: (b-a)/s+1}, (_,i)=>a+i*s)

  3. Frequency of benefit? Frequent (building arrays, functional loops)

  4. Expected improvement? There are possible performance benefits to have a range/seq native function, but the main benefit is avoiding extra packages for something frequent

  5. Userland implementable? it can definitely, in one line. The drawback is having to rely on an external package, to import it. leftpad now implemeted as String.prototype.padStart is an example case

  6. Implementable? Does it present potentially difficult or intractable implementation challenges? No

  7. Consistent? Is it consistent with existing syntactic and semantic practices in the languages? Yes

  8. Holistic? Does it fill in some obvious logical gap in the current language design? Yes, for creating ranges. There are Array.from({length: n}, (_, i) => doSomethingWwith(i)) or other similar non-obvious code for creating ranges. A new seq or range function would fill the gap between Array constructor which is not usable (because it creates sparse arrays) and Array.from or Array.prototype.fill which aren't practical enough for creating ranges

  9. Understandable? Does it place an unsustainable new "cognitive burden" on learners and users of the language? No, rather the opposite

  10. Library? Is is something that would be better provided as part of some kind of future standard library? I don't think so, because it's short, it may be a static Array function, or a global

  11. Intrusive? Does it take over real estate that might be useful for future features no one has thought of yet, the obvious example being using special characters? No, it's a simple drop-in

  12. Readability? Is it something that results in a distinct improvement in readability or visible semantic correctness of code? Yes, as described, Array.from({length: n}, (_, i) => .. ) is not readable and practical enough

  13. Prior art? Has this or a similar feature already been proposed, and if so what was the reaction, and how is your proposal different from that, or from a similar features existing in other languages?

  • Array.build was proposed at the time ES6 was discussed I think gist.github.com/rwaldron/11186883, it's a different approach for creating ranges, but already an improvement over Array.from({length: n}, (_, i) => .. )
  • Array comprehensions were proposed at the time of ES6 as well, and rejected, reasonably in my opinion
  • languages like python has a range builtin, I can also think of the seq linux command, I wouldn't be opposed to a new syntax addition like [1:10] as well

There were maybe proposals for it in the past, but I didn't find any so far, I could try to create one. Some of you seem to want generators for this, I'd prefer not

Le dim. 1 juil. 2018 à 08:03, Bob Myers <rtm at gol.com> a écrit :

# Cyril Auburtin (6 years ago)

s/possible benefits/possible performance benefits/ sorry

Le dim. 1 juil. 2018 à 18:13, Cyril Auburtin <cyril.auburtin at gmail.com> a écrit :

# Cyril Auburtin (6 years ago)

I really like this possible new syntax for ranges:

[2:9] // [2,3,4,5,6,7,8,9]
[1, 2, 4:7, 9] // [1, 2, 4, 5, 6, 7, 9]
[1:6:2] // [1, 3, 5]

Would someone in TC39 be up to champion this?

# Cyril Auburtin (6 years ago)

I started wrapping the idea here caub/proposal

# Cyril Auburtin (6 years ago)

Range literals could take form in tc39/proposal-slice-notation#19