The New Iterator.range Proposal for JavaScript Sequences
Sometimes the simplest proposals are the ones that bring the most value to a project, and this one was no different.
Several languages, Python among them, for example, have structures to represent and even create sequences of natural numbers in a simple way. JavaScript didn’t have this capability yet, and it had been heavily requested across many projects.
But the wait is over. Today we’re going to talk about the proposal that will add a range method to an iterator. This proposal is still early on (it hasn’t passed stage 1), but it’s extremely promising!
If you don’t know how JavaScript works, in this video I explain a bit more about the process behind shipping new JavaScript features. If you haven’t watched it yet, I strongly recommend it so you can understand how everything works!
Ranges#
Ranges, or sequences, are meta-structures of a language. That is, they exist in theory, but they’re essentially just an encapsulation of simpler logic turned into a language construct. Ranges are simply a sequence of numbers, with a minimum value, a maximum value, and an optional value called step, which is how many numbers we skip each time.
Ranges are useful for a variety of things, but one of the main uses is as an alternative to an iterator. Instead of doing something like:
for (let i = 0; i < 10; i++) { ... }You could make a simpler call. For example, in Python, you can generate a range of numbers going from 0 to 9 using this code:
for n in range(10): print(n)This code prints numbers from 0 to 9 in the console, but you can do the same thing using a plain for and counting from 0 to 9. In JavaScript, for example, it would look something like this:
let x = [0]for (let i = 0; i <= 9; i++) { x.push(i+1)}That’s more steps and more code, and that’s exactly why this proposal exists. It doesn’t even have any special goal of improving performance or making counting more efficient, it’s purely and simply because every other language has one, and a language without a range feels incomplete.
This is one of those functions that’s super simple and has a few specific uses you can’t escape, the same way with the intersection of two arrays, or the difference between them. It’s too simple not to be built into the language.
In fact, there’s a whole Stack Overflow question with more than 20 different implementations of a range. The one I like the most is a simple, self-contained implementation that uses an array’s keys as values:
[...Array(10).keys()]// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9I even made a post a while back showing how we can implement iterators to build a range function, which is exactly how this proposal is built.
Iterator.range#
The idea of how the proposal will be implemented is still under discussion. The two options would be:
- Implementing the range method inside the
Numberclass, resulting in something likeNumber.range - Creating a new class called
Intervalthat would contain other kinds of methods besides range
This is an open discussion that you can even take part in!
Personally, I don’t lean much toward either side, but depending on the use ranges are meant to have, I’d say I’m more in favor of the method than the class, at least for now.
Assuming the original proposal, using a method on the Number class, is winning out, the proposal will add a range function with this signature on both Number and BigInt:
class Number { range(start: number, end: number, options: { step: number = 1, inclusive: boolean = false } | step: number = 1): RangeIterator;}
class BigInt { range(start: bigint, end: bigint | Infinity | -Infinity, options: { step: bigint = 1n, inclusive: boolean = false } | step: bigint = 1n): RangeIterator;}This means we’ll be able to do something like this:
for (const i of Number.range(1, 10)) { console.log(i); // => 1, 2, 3, 4, 5, 6, 7, 8, 9}Or even simplify things a bit by destructuring the property:
const { range } = Numberconsole.log(...range(1, 10, 2))Where the third parameter can be either the step or an options object containing these properties:
step: How many numbers we should skip each time. This can be a negative number, so you can create ranges that start, for example, at 100 and go down to 0 withrange(100, 0, -1)inclusive: a property that defines whether the final value is included in the count. For example,range(100, 0, -1)would count from 100 down to 1, excluding0, whilerange(100, 0, {inclusive: true, step: -1})would count from 100 down to 0.
Future implementations#
This proposal works really well with another proposal called slice notation, which adds a [start:end] notation to JavaScript. In the proposal, one of the main examples deals with arrays, for example:
const arr = ['a', 'b', 'c', 'd'];
arr[1:3];// → ['b', 'c']
arr.slice(1, 3);// → ['b', 'c']In other words, the idea is to replace direct use of slice. In the case of ranges, though, we could do something like this:
const x = 1:3 // [1,2,3]Which would be the same as using range(1, 3, { inclusive:true }). And this seems like quite an interesting proposal, especially if we can do something like this:
for (let x in 1:100) {}Conclusion#
Even though the proposal is recent and still under discussion, I believe it’ll be enough to change how we work with JavaScript, at least in a subtle way.
If you want to try this proposal out, the core-js package contains polyfills for these functions. Just run npm install core-js in your package and create a file like this index.mjs:
import 'core-js/proposals/number-range.js'const { range } = Numberconsole.log(...range(100, 0, -1))I strongly recommend running your own tests and finding other uses for range. I also invite you to comment on the proposal with your opinions on how this API should look!
Liked this idea? Leave a comment here with your uses for range and hit me up on my social media, I’d love to talk about it!