For most people reading this article, the imperative programming style comes naturally, as it's the one we were taught. It also very closely matches the simplest view of how a computer works: you give it a list of instructions and let it progress through that list in order until complete. But just because it seems simplest at first blush, doesn't mean it's as simple as it can be.
Be kind to future you
When writing code, you will often have different priorities. Sometimes you will write code that must perform quickly; sometimes you will write code that must constrain itself to a limited memory space. Most frequently, you should be writing code that will be easy to read and maintain later, so clear and simple code should take priority over clever or obtuse code. Brian Kernighan puts it best: "Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it."
Thankfully, by learning a little bit of declarative programming (specifically, some functional programming), we can clean up some too-clever or too-obtuse code and make it a lot easier to read and maintain later.
A note about language
Example #1: How not to run a newsletter
Let's start off easy. Assume that you need to put together some HTML for a newsletter you're planning on sending out. You want a series of paragraphs with a horizontal rule between them. There should not be a horizontal rule before the first paragraph, or after the last. A traditional, imperative, for-loop-based approach might look like:
We're iterating over all the paragraphs we want to display and adding a horizontal rule before it, but only if it's not the first paragraph being added. This prevents us from having a horizontal rule before the first paragraph at the expense of tracking another variable. Alternatively, we could have used a for loop that iterated over the array's indexes (
for (let pIdx = 0; pIdx < paragraphs.length; pIdx++)) and checked if the index was
0, but that doesn't reduce complexity either - we still have the extra variable, and now we have to access the array by index.
join() simply sticks together the array values by the provided value, giving us the same number of horizontal rules in the same place as our array example. But the eagle-eyed among you will likely notice a bug - the output isn't the same here. We haven't surrounded our paragraphs with
<p> tags. Thankfully, there's another functional array method that we can take advantage of -
map does is it iterates over the array, applying the function we provide it to every member, and returns the new array. Essentially, once
map is complete, we have an array of our paragraphs - surrounded by
<p> tags - that we then feed into the
Once you know how
join work, this example is far easier to read than the first one. There are fewer variables to keep track of, no looping logic to mentally evaluate, and fewer conditionals to parse. In fact, it reads fairly closely to English: "Start with our list of paragraphs, wrap them in
<p> tags, and join them with
<hr> tags. Got it." This code will be immensely simpler to maintain in the future, should you need to modify how paragraphs are displayed or broken up.
Example #2: Departments before departing
Pretend it's Monday. You've just gotten back from the break room, cup of coffee in hand, ready to start the day. Suddenly, your boss shows up out of nowhere. "Debra!" he cries. (Pretend your name is Debra.) "Debra, I need to know how much the Garden department sold over the weekend and I only trust you to figure it out!"
So. Typical Monday.
Thinking iteratively at first, you come up with the following solution:
Not bad! Gets the right number, and it's not that complicated. However, you know your boss, and you know that he's going to want some modifications as soon as he sees it, so it'd be good to have something simple and easy to modify when he does. Taking a quick swing at it, you come up with:
filter is an array method that takes a function as an argument and will return an array composed only of the entries of the original array that return
true when passed to the function. In our case, we're just checking the make sure that the department of the sale is
Garden. Then, we use
reduce, an array method that is a little trickier, but basically just takes a function and a starting value, and the function has the current total and the array entry as parameters. This lets us add the price of all entries to the total and return it as the result.
However, it's not the clearest, is it? You have to do a fair bit of reading to figure out what each line does. We could add some comments to make things clearer, but since we're passing functions to
reduce, and function names are self-documenting, we have a slightly more elegant solution to hand:
Just as you put the finishing semicolon on there, your boss rushes past your desk, hurriedly shouting "Actually-I-need-all-the-outdoor-departments-summed-together-thanks-Debra!" before he shuts himself into a conference room with what looks like federal auditors? Oh well, above your pay grade, you figure, and get back to your program. After only a little tweaking, you settle on:
That conditional in your original iterative attempt would have started looking a little care-worn now, and would have started fraying intensely with any more departments added in, but this pattern remains pretty clear with as many departments as you want to add. Plus, all those
isDepartment methods are pretty similar and could be moved to and exported from a module to keep this file clean.
After running it a couple times in succession to see if you can catch some stray cosmic rays hitting your processor and causing it to give out an incorrect calculation, your boss slouches out of the conference room. "Hey Debra, nevermind that calculation. Actually, you may as well head home for the day. Just... make sure to take anything with you that you want to keep."
Sweet! A day off!
The right tool
Now, it's not that you can't make clean and maintainable iterative code. It's just that functional code leads very naturally to clean and maintainable code. It forces you to break down your problems into smaller, discrete chunks, which are easier to reason about than one big loop. To use a ridiculous analogy, you could use a sports car to transport building materials, but you're going to have to make a lot more trips than with a truck. And to use an even more ridiculous analogy, you could use a truck to set a landspeed record, but you're going to have to strap a lot of propellant on there, and it'll get real dangerous, real quick.
The point is, using the right tool can make your job significantly easier. When your job is writing code, you will most frequently be prioritizing writing clean, maintainable code. Using a coding paradigm that lends itself naturally to that priority will benefit you in the long run.
So the next time you find yourself reaching for a for loop, consider a functional alternative, and do future you a favour.
Hey! Are you a SaaS founder or a founder-to-be and wish you could skip all the boilerplate that comes from starting a new SaaS app? Well I'm working on a SaaS starter kit called Nodewood that might interest you! Save weeks or months of time and start writing business logic now!