Functional Ranges for Loops - Generating Better Loops
Tags: javascript, functional, beginners, webdev
Published: 2022-02-06 23:06:57

_by author [Federico Kereki](https://blog.openreplay.com/authors/federico-kereki/)_


Working with loops is quite common in procedural programming.  But, applying some Functional Programming (FP) techniques may lead to better, clearer coding. In this article, we're going to discuss common loops, and how they may benefit from giving an FP-look to them.

## Functional-style loops

Modern JavaScript provides several ways of working with implicit loops. For instance, applying some logic to all elements of an array may be done with [`.map()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) to produce a new array as a function of the original one, or [`.reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) to produce a result out of all the array elements. There are [even more](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#): `some()`, `every()`, `filter()`, `find()`, etc., all of which perform loops implicitly.

These methods are quite common in FP. Also, they are to be preferred because they allow you to program in shorter, more declarative ways. For instance, if you read `someArray.map(v => aFunction(v))` you already know that the developer wants to apply a certain function to every element in some array to generate a new, transformed one. (A short aside: FP coders would probably write the sample code above as `someArray.map(aFunction)` -- check our [previous article on Pointfree Style Programming](https://medium.com/stackanatomy/forever-functional-pointfree-style-programming-c3877c229ceb) for more on this.) Of course, you still have to understand what the mapping function does. But, you won't have to worry if the loop limits are correct, if there are ["off-by-one" bug](https://en.wikipedia.org/wiki/Off-by-one_error), if the function is applied correctly, etc.

When these operations aren't appropriate, you still can resort to  more general `.forEach()` loops. Along the same lines of the previous example, if you see code like `someArray.forEach(v => doSomething(v))` you know that it will be doing something for each and every element of the array, from the first to the last. It's possibly not so clear what it's doing (it's obvious that there is some kind of side effect --as we discussed in [another previous entry in this series](https://medium.com/stackanatomy/forever-functional-injecting-for-purity-eb4997821510)-- because otherwise the code wouldn't do anything at all - but what?) but still, using `.forEach()` allows us to be sure that no element in the array will be missed, or that the code won't attempt to access elements beyond the last one, or similar common bugs.

All the loops in this section are, then, good in practice. But they aren't the more common type of loop!

## Procedural-style loops

Programming is usually taught in [imperative style](https://en.wikipedia.org/wiki/Imperative_programming) so it's fairly common to see general loops written like the following.

```javascript
for (let i=9; i<=22; i++) {
  // do something with i
}
```

Old-fashioned developers may even eschew this kind of loop, and go for a `while` construct!

```javascript
let i = 9;
while (i <= 22) {
  // do something with i
  i++;
}
```

With enough experience, you can recognize these patterns and understand the implied process. In these loops we want to do something to every value in the range from 9 to 22 inclusive. However, we may make some points.

* Off-by-one bugs are more probable. For example, you might write `i<22` instead, and then the loop would end at 21, not 22.
* If you happen to modify the index variable `i`, the loop may go on forever or suddenly stop.
* Code is longer (more lines) than with the functional-style loops.

So, can we do better with some FP-style code? Let's see how!

## Functional-style ranges and looping

We have to figure out how we want to work. If we had a function that produced an array with the values to process, then we could rewrite the loop as here.

```javascript
range(9, 22).forEach(i => { 
    /* do something with i */ 
})
```

Our `range()` function would have `from` and `to` parameters, and generate an array with values starting at `from` and ending at `end`. A first version could be as follows.

```javascript
const range = (from, to) => {
  const arr = [];
  do {
    arr.push(from);
    from++;
  } while (to >= from);
  return arr;
};
```

This works well: `range(9,22)` produces an array `[9, 10, 11, ... 22]` as desired. A call with equal arguments also works: `range(60,60)` gets `[60]`. There's a missing case: what about a reverse range, like `range(12,4)`? We could then also want fractional steps, so a generalization is in order.

```javascript
const range = (from, to, step = Math.sign(to - from)) => {
  const arr = [];
  do {
    arr.push(from);
    from += step;
  } while ((step > 0 && to >= from) || (step < 0 && to <= from));
  return arr;
};
```

We're done! This version handles all kinds of ranges, and you can even do something like `range(2, 4, 0.6)` and get `[2, 2.6, 3.2, 3.8]`. You may even skip providing the loop step, and everything will still work. However, there are some common loop features that we lack... What about [breaking out](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/break), or [skipping ahead](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/continue)? Let's see if we can do anything about those.

## Jumping around

Documentation [for forEach() loops](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) is clear: *"There is no way to stop or break a forEach() loop other than by throwing an exception. If you need such behavior, the forEach() method is the wrong tool."* There's no simple way to emulate a break or continue. Throwing an exception (and catching it out of the loop) would do for a break, but it's a bit wordy, and not really the way to use exceptions!

There's another "dirty trick": we could use [.some(fn)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) instead of `.forEach(fn)`. As long as the `fn` function returns a falsy value, the loop will continue; when it returns a truthy value, the loop would end. We could thus write code as shown below -- but don't, please wait!

```javascript
range(9, 22).some(i => {
  ...
  ...
  if (someCondition) return false; // <-- a "continue"
  ...
  if (somethingElse) return true;  // <-- a "break"
  ...
  ...
})
```

Is this good code? It works, sure, but we're using `.some()` in an unintended way. Readers of the code would just become frustrated -- and understanding those special `return` statements is not so easy. No, we need a better solution, and fortunately JavaScript has the right tool: [generators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator).

## Open Source Session Replay
Debugging a web application in production may be challenging and time-consuming. [OpenReplay](https://github.com/openreplay/openreplay) is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue.
It’s like having your browser’s inspector open while looking over your user’s shoulder.
OpenReplay is the only open-source alternative currently available.

![OpenReplay](https://raw.githubusercontent.com/openreplay/openreplay/main/static/replayer.png)

Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay).



## Generating ranges

Generators are functions that can be exited but also re-entered at a later time. Each time you call a generator it will `yield` (return) a value, or it won't return anything signaling that there are no more values. Code for our `range()` solution would be as follows. In particular, note the `function*` declaration, which implies a generator function.

```javascript
function* range(from, to, step = Math.sign(to - from)) {
  do {
    yield from;
    from += step;
  } while ((step > 0 && to >= from) || (step < 0 && to <= from));
}
```

Generators cannot be used with `.forEach()`, but they work with [`for..of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) statements. We should now write code as follows.

```javascript
for (const i of range(9, 22)) { i => { 
    /* do something with i */ 
  }
}
```

Quite clear! You may also directly use `break` and `continue` statements.

```javascript
for (const i of range(9, 22)) { i => { 
  ...
  ...
  if (someCondition) continue;
  ...
  if (somethingElse) break;
  ...
  ...
  }
}
```

Oh, and if you actually wanted an array, you may spread out the generator results as follows.

```javascript
const arrayFrom9To22 = [...range(9, 22)];
// this produces [9, 10, 11, ... 22]
```

Finally, by using generators we gain a tad of performance, because we won't be (first) generating an array, and only then (second) processing it. You may even work with truly large ranges without requiring proportionate memory: `range(9,999999999999)` won't cause any memory problems!

## Summary 

We have seen how to make common loops clearer by using a range-producing function, which allows us to write code more succinctly. Our first solution (producing arrays) was simple but had some limitations because it didn't allow us to use common statements to break out of loops, for example. The final solution we considered, using generator functions, was free from those limitations and also had better performance. Get used to writing code in this fashion, and your code will be both shorter and clearer.