Joel Thoms

@joelthoms

Rethinking JavaScript: Replace break by going functional

In my last article, Death of the for Loop, I tried to convince you to abandon the for loop for a more functional solution. In return, you raised a great question, “What about break?”

break is the GOTO of loops and should be avoided.

break should be placed in the rubbish bin in the same exact way GOTO has.

You may be thinking, “Come on Joel, surely you are just being sensational. How is break like GOTO?”

// bad code. no copy paste.
outer:
for (var i in outerList) {
inner:
for (var j in innerList) {
break outer;
}
}

I am offering labels as the proof. In other languages, labels were the counterpart to GOTO. In JavaScript, label is the counterpart of break and continue. Because break and continue come from the same label family, this also makes them very close relatives of GOTO.

JavaScript’s label, break and continue are leftover legacy from the days of GOTO and unstructured programming.
xkcd

“But It’s not hurting anyone, so why not leave it in the language so that we have options?”

Why should we place limits on how we write software?

It sounds counter-intuitive, but limits are a good thing. The removal of GOTO is a perfect example of this. We also welcomed the limitation “use strict” with open arms and even lecture those who don’t use it!

“limitations can make things better. A lot better. “— Charles Scalfani

Limits make us write better software.

What are our alternatives to break?

I’m not gonna sugar coat this, there is no one size fits all quick and easy replacement. It’s a completely different way of programming. A completely different way of thinking. A functional way of thinking.

The good news is there are many libraries and tools out there to help us like Lodash, Ramda, lazy.js, recursion, etc.

We’ll start with a simple collection of cats and a function called isKitten. These will be used in all of the examples to follow.

const cats = [
{ name: 'Mojo', months: 84 },
{ name: 'Mao-Mao', months: 34 },
{ name: 'Waffles', months: 4 },
{ name: 'Pickles', months: 6 }
]
const isKitten = cat => cat.months < 7

Let’s start out with a familiar old-school for loop example. This will loop through our cats and break when it finds the first kitten.

var firstKitten
for (var i = 0; i < cats.length; i++) {
if (isKitten(cats[i])) {
firstKitten = cats[i]
break
}
}

Now let’s compare that to the lodash equivalent.

const firstKitten = _.find(cats, isKitten)

OK, so that example is fairly simple. Let’s kick it up a notch and try something a little more edge case. Let’s enumerate our cats and break after we have found 5 kittens.

var first5Kittens = []
// old-school edge case kitty loop
for (var i = 0; i < cats.length; i++) {
if (isKitten(cats[i])) {
first5Kittens.push(cats[i])
    if (first5Kittens.length >= 5) {
break
}
}
}

The easy way

lodash is amazing and does tons of great things, but sometimes you need something more specialized. This is where we bring in a new friend, lazy.js. It’s “Like Underscore, but lazier”. And lazy is what we want.

const result = Lazy(cats)
.filter(isKitten)
.take(5)

The hard way

Libraries are all fun and games, but sometimes what’s really fun is to create something from scratch!

So how about we create a generic function that will act like filter and also add the limit functionality.

The first step is to encapsulate our old-school edge case kitty loop in a function.

Next, let’s generalize the function and extract out all the cat specific stuff. Replace 5 with limit, isKitten with predicate and cats with list. Then add those as parameters to the function.

Now we have a working and reusable takeFirst function that has been completely separated from our cat business logic!

Our function is now also a pure function. This means the output is derived solely from the inputs. Given the same inputs, it will produce the same output 100% of the time.

We still have that nasty for loop in there, so let’s keep refactoring. The next step is to move i and newList to the argument list.

We want to break the recursion (isDone) when the limit reaches 0 (limit will count down during the recursive process) or when we reach the end of the list.

If we are not done, then we check to see if our filter predicate has a match. If we get a match then we’ll call takeFirst, decrement limit and append to our newList. Otherwise, move onto the next item in the list.

If you have yet to read Rethinking JavaScript: The if statement, it will explain this final step of replacing the if's with a ternary operator.

Now we can call our new method like this:

const first5Kittens = takeFirst(5, isKitten, cats)

For extra credit we could curry takeFirst and use it to create other functions. (more on currying in another article)

const first5 = takeFirst(5)
const getFirst5Kittens = first5(isKitten)
const first5Kittens = getFirst5Kittens(cats)

Summary

There are many great libraries we can use like lodash, ramda or lazy.js. If we are so daring, we can even create our own methods using recursion!

I must warn that while takeFirst is super awesome, recursion comes from a monkey’s paw wish. Recursion in JavaScript-land can be very dangerous and it’s easy to get the infamous Maximum call stack size exceeded error message.

I’ll be covering recursion in JavaScript in my next article. Stay tuned.

I know it’s a small thing, but it makes my day when I get those follow notifications on Medium and Twitter (@joelnet). Or if you think I’m full of shit, tell me in the comments below.

Cheers!

Related articles

More by Joel Thoms

Topics of interest

More Related Stories