In this post, I delve into writing the same functionality in a myriad of ways. In the process, I'll evaluate some trade-offs, as well as try out generators of Javascript ES6.

The problem I'm solving is simple: we're building an interaction that plays musical sounds based on the users' actions. The details aren't important here, but I need to generate the frequencies for six octaves of "A" notes. My pair programmer (Aila) and I write simply:

`const octaves = [110, 220, 440, 880, 1760, 3520]`

### Refactor, pass I

This is OK, but not as maintainable as it should be. To change the key of the notes, my future self willg have to change all the numbers. I’d rather express the underlying math if possible.

Turns out that this can be done with an old fashion `for`

loop.

```
const octaves = buildOctaves(110, 4000) // "110" is base/bass note
function buildOctaves(lo, hi) {
const o = []
for (let f = lo; f < hi; f *= 2)
o.push(f)
return o
}
```

That works. It's not super because of all the noise there-- the
temporary `const`

and the `for`

-loop hide the `* 2`

which is the
essence of it. Does
recursive work better? No, but sometimes you don't know until you try:

```
function buildOctavesRecursive(lo, hi) {
return (function next(f) {
if (f < hi) {
const n = next(f*2)
return n ? [f].concat(n) : [f]
}
})(lo)
}
```

### ES6

I’ve been using RxJS lately, and thought about using it. It's great
at generating sequences and would handle this fine. But RxJS's
sweet spot is asynchronous event streams, and this is much simpler.
In fact, it seems like a great fit for the new *iterators* and
*generators* functionality in ES6.

I liked the idea of using the spread (`...`

) operator to execute
the loop, to get rid of the `while`

construct. I find some
examples of building generators online, and after realizing
I don't need to use `[Symbol.iterator]`

, I get:

```
function buildOctaves(lo, hi) { // from Generator, take II
const iterator = function* () {
let a = lo
while (a < hi) {
yield a
a *= 2
}
}
return [...iterator()]
}
```

I still have a while loop, but this is readable and transparent about the intent of the code. I even like the inlined version:

```
function buildOctavesGenerator(lo, hi) {
return [...(function* () {
let a = lo
while (a < hi) {
yield a
a *= 2
}
})()]
}
```

It's nice that the different aspects of the logic are each on their own expressive line:

`return [...function* () () {`

--*return an array generated by...*`while (a < hi)`

--*while our accumulator is below the limit*`a *= 2`

--*multiply by two*

A note about using new features: Admittedly if the new features of
Javascript are new to you, this is the *least* readable. But we need to be careful here. We need to
ask, is it hard to read because of unfamiliar language features, or how
the logic is spelled out? With a few exceptions, we need to assume that
the reader understands the features of the language. Otherwise, we go down the path of writing code for the least common denominator: we limit
ourselves to a small, arbitrary subset of
any programming language. (The exception of this is complex languages
like C++, where a style guide is needed to highlight which features
of the language that one particular project will use.)

But I want to try one more pass, and see if the logic can be made
clearer.
The `while`

and `yield`

are a distraction from the simple math here.

### Top down

To get at the most readable code, often you need to start with the
final desired *ideal* code. So I sketch out what this might look
like:

```
function buildOctaves(lo, hi) {
const octaveIter = doubleIter(lo)
const notes = whileLessThan(octaveIter, hi)
return [...notes]
}
```

The whole octave math is based on doubling the lower octave, so it makes sense as a basic generator to build upon. That needs to be coupled with something to halt the iteration.

The doubling iterator is obvious:

```
const doubleIter = function* (x) {
while (true) {
yield x
x *= 2
}
}
```

Nice! And the `whileLessThan`

can be:

```
const whileLessThan = function*(it, max) {
for (x of it) {
if (x < max)
yield x;
else
return
}
}
```

This is okay, but I realized that this function is doing two different things, and parameterizing the loop with a function allows us to do the same thing with functional composition:

```
const takeWhile = function* (it, fn) {
for (x of it) {
if (fn(x))
yield x;
else
return
}
}
const whileLessThan = function(it, max) {
return takeWhile(it, (x) => x < max)
}
```

### Conclusion

For me, since the last few functions I extracted are semantically clear and can be moved into a library, the decomposed solution using the latest Javascript generators really is the best one. It provides the clearest abstraction of logic as well as the easiest refactor paths.

What do you think?

## No comments:

Post a Comment