Implementing ranges in JavaScript
Tags: javascript
Published: 2021-09-14 16:56:24

---
title: Implementing ranges in JavaScript
published: true
description: 
tags: javascript
cover_image: https://images.unsplash.com/photo-1521080755838-d2311117f767?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1429&q=80
canonical_url: https://www.antoniovdlc.me/implementing-ranges-in-javascript/
---

Ranges are natively supported by a few (popular) programming languages. They allow for iteration over a defined space, while not have a linear increase in their memory footprint (all ranges always store a similar amount of data).

Let's try adding a similar idiom to JavaScript!

---

One way to approach this challenge is to write a plugin for a transpiler (for example a babel plugin) that would allow the following syntax:

```js
const range = (0..5)
for (let i of range){
  console.log(i)
  // 0, 1, 2, 3, 4
}
```

Instead, we will provide a similar functionality with vanilla JavaScript.

```js
for (let i of range(0, 5)) {
  console.log(i)
  // 0, 1, 2, 3, 4
}
```

> We does the range stop at `4` instead of `5`? This is a design choice, and as most languages that have a built-in `range` as not inclusive of their last value, the `range` utility that we will build will follow a similar convention by default.

The above syntax also allows us to pass a third argument to the function to control the `step` in between each iteration:

```js
for (let i of range(0, 10, 2)) {
  console.log(i)
  // 0, 2, 4, 6, 8
}
```

---

To start, let's create a class `Range` which will host the data needed for a range:

```js
class Range {
  constructor(start, stop, step = 1) {
    this._start = Number(start);
    this._stop = Number(stop);
    this._step = Number(step);

    // Initialise a counter for iteration
    this.i = Number(start);
  }
  
  first() {
    return this._start;
  }
  
  last() {
    return this._stop;
  }
  
  step() {
    return this._step;
  }
}
```

We can now create a very basic (and not very useful) range:

```js
const range = new Range(0, 10);

range.first(); // 0
range.last(); // 10
range.step(); // 1 (by default)
```

One of the main reasons we want ranges though is to iterate over them ... so let's implement [iteration protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) in our `Range` class!

To do so, we need to implement a `next()` method, as well as a `[Symbol.iterator]` method.

```js
class Range {
  constructor(start, stop, step = 1) {
    ...

    // Initialise a counter for iteration
    this.i = Number(start);
  }
  
  first() { ... }
  last() { ... }
  step() { ... }
  
  next() {
    if (this.i < this._stop) {
      const value = this.i;
      this.i += this._step;
      return { value, done: false };
    }

    return { value: undefined, done: true };
  }
  
  [Symbol.iterator]() {
    return this;
  }
}
```

Great! Now we can use our ranges as follow:

```js
const range = new Range(0, 5)

for(let i of range) {
  console.log(i)
  // 0, 1, 2, 3, 4
}
```

or

```js
const range = new Range(0, 5)

range.next() // { value: 0, done: false }
range.next() // { value: 1, done: false }
range.next() // { value: 2, done: false }
range.next() // { value: 3, done: false }
range.next() // { value: 4, done: false }
range.next() // { value: undefined, done: true }
```

There is one issue with our current implementation though, and that is that the range is depleted after a single iteration. We cannot reuse the same range in multiple consecutive loops.

Luckily, there is a one line fix to support that:

```js
class Range {
  constructor(start, stop, step = 1) {
    ...

    // Initialise a counter for iteration
    this.i = Number(start);
  }
  
  first() { ... }
  last() { ... }
  step() { ... }
  
  next() {
    if (this.i < this._stop) {
      const value = this.i;
      this.i += this._step;
      return { value, done: false };
    }
	
    // We reset the value once we have iterated over all values so that
    // ranges are reusable.
    this.i = this._start;

    return { value: undefined, done: true };
  }
  
  [Symbol.iterator]() {
    return this;
  }
}
```

Finally, to achieve the semantics we defined at the beginning, we need to wrap our class creation in a function:

```js
class Range { ... }

function range(start, stop, step = 1) {
  return new Range(start, stop, step);
}

for (let i of range(0, 5)) {
  console.log(i)
  // 0, 1, 2, 3, 4
}
```

---

Again, inspired by this blog post, I decided to build a library with the aforementioned features and much more! Check it out:

{% github AntonioVdlC/range %}