And it should be: The benefits for readability, maintainability, and testability are dramatic.

"Functional programmming" means many things to many people. One of my favorite tools from the FP mindset is programming in a list-processing style. This entails taking arrays—or lists, as I prefer to call them—as your fundamental data structure.

Table of Contents

Then, your program becomes a series of operations on elements in the list. And there are many contexts in which this is useful: Mapping AJAX results to React components with map. Removing extraneous data with filter. Doing...Well, fancy things with reduce.

These functions, called "Array Extras", are abstractions over for loops. There is nothing you can do with with these functions that you can't achieve with for, and vice-versa.

There are some finer points as to their usage, though, which are important to keep in mind as you cultivate an increasingly functional mindsetn.

Today, we'll lay some ground work by taking a look at all three of these functions. We might even meet an extra function or two, just for good measure.

But, we'll never figure out what's in store if we don't get started. So let's get started with something you already know: The good ol' for loop.

In this snippet, we start a list of lowercase names. Then, we initialize an empty array, in which we'll store our capitalized strings.

Inside of the for loop, we simply grab the next string on every iteration; capitalize it; and push it into capitalizedStrings. At the end of the loop, capitalizedStrings contains every word in strings, but...You know, capitalized.

This brings us to our first function: forEach. This is a method on arrays that "automatically" loops through the list for us. In other words, it handles the details of initializing and incrementing a counter for us.

Instead of the above, where we manually index into strings, we can simply call forEach, and receive the next string on every iteration. The updated version would look something like:

...This is almost the same as what we started with. But getting rid of that i is already a big win.

Free Node eBook

Build your first Node apps and learn server-side JavaScript.

📧

Thank you!

You have successfully joined the Scotchy super duper web dev awesome mailing list.

This also introduces a major pattern we'll see time and time again. Namely: We prefer to use methods on Array.prototype that abstract away details like initializing and incrementing counters. That way, we can focus on the important logic, rather than the boilerplate.

The idea is that, if I send you a normal message, like 'this is my super secret message', and someone else gets their hands on it, they can read the secret immediately. This obviously sucks if you're sending sensitive information, like passwords, which someone might be listening for.

...And, yes. Someone is always listening.

Encrypting a string means: "scrambling it to make it hard to read without unscrambling." This way, even if someone is listening, and even if they do intercept your message, it will remain unreadable until they unscramble it. To quote the example above, it's pretty non-obvious what the string 'vjku ku oa uwrgt ugetgv uvtkpi.' is supposed to mean.

The Caesar cipher is one of the simplest ways to scramble a string like this. To encrypt with the Caesar cipher, we pick a key, n, between 1 and 24, and replace every letter in the original string with the one n letters further down the alphabet.

Substituting letters like this makes the original string pretty unreadable. But, we scrambled by simply shifting letters. So, all you need to do to unscramble is shift them back. If you get a message you know was encrypted with the key 2, all you need to do to decrypt is shift letters back two spaces. So, c becomes a; d becomes b; etc.

Unfortunately, this is a pretty useless form of encryption nowadays. It's extremely easy to break. An easy way to decrypt any string encrypted with a Caesar cipher is to just try to decrypt it with every possible key. One of the results will be correct. All you need to do is scan the list of 24 outputs for the one that's English. The others will be just as unreadable as the original string.

Below, I'll use a function called tryAll, which does just that. Namely: It takes an encrypted string, and returns a list of every possible decryption. One of those results will be the string we want. So, this always breaks the cipher.

Of course, scanning a list of 24 possible decryptions...Kind of sucks. It sort of feels like we should be able to automatically throw away the ones that are obviously wrong.

In fact, it's easy to do that. In the section on filter, you'll see a function, called isEnglish, which does just this. In particular, it reads a string; counts how many of the most common 1,000 words in English occur in that string; and, if it finds more than 3 of those words in the sentence, it classifies the string as English. If the string contains fewer than 3 words from that list, it throws it out as "junk".

There are more sophisticated ways to check if a string is English, of course, but this is fine for now.

The implementations of tryAll and isEnglish aren't important, but if you're curious, you can find them at this gist: tryAll / isEnglish.

Refactoring a for loop to use forEach hints at advantages of this style. But there's still room for improvement in the updated version.

In the example above, we're updating capitalizedStrings within the callback to forEach. There's nothing inherently wrong with this. But, it saves a lot of headache to avoid side effects like that whenever possible.

If we don't have to update data structures that live in a different scope...We probably shouldn't.

In this particular case, we wanted to turn every string in strings into its capitalized version. This is a very common use case for a for loop: Take everything in a list; turn it into something else; and collect the results in a new list.

Transforming every element in a list into a new one and collecting the results is called mapping. JavaScript has a built-in function for just this use case, called, appropriately enough, map.

We used forEach because it abstracts away the need to manage the iteration variable, i. We don't have to manually index into the array, etc., and so we can focus on the logic that really matters.

Similarly, we use map because it abstracts away initializing an empty array, and pushing to it. Just like forEach accepts a callback that does something with each string value, map accepts a callback that does something with each string value.

Let's look at a quick demo before a final explanation. In this example, I'm using a function to "encrypt" a list of messages. We could use a for loop, and we could use forEach...But, it's better with map.

Another common pattern is using a for loop to process items in a list, but only pushing/preserving some of them.

We usually decide which items to keep, and which to throw away, by doing an if check.

In raw JS, this might look like:

// Secret message! This was a string encrypted with a key between 1 and 24.const encryptedMessage ='mduqxxq, mdq kag ftqdq?'];// We can break this code by just decrypting with _every_ possible key.const possibilities =tryAll(encryptedMessage);// Most of these decryption attempts aren't readable. Sad.// We can speed up finding the ones that are probably junk with an if checkconst likelyPossibilities =[];
possibilities.forEach(function(decryptionAttempt){// Keep the string if it looks like an English sentenceif(isEnglish(decryptionAttempt)){
likelyPossibilities.push(decryptionAttempt);}})

First, we try to decrypt the encrypted message with every possible key. That means we end up with an array of 24 possibilities.

In this loop, we test each one to see if it looks like an English string. If so, we keep it. If not, we throw it away.

This is clearly a common use case. So, there's a built-in for it, aptly named filter.

As with map, we pass filter a callback, which also gets each string. The difference is that, filter will only save items in an array if the callback returns true. So, we could express the above as:

// Secret message! This was a string encrypted with a key between 1 and 24.const encryptedMessage ='mduqxxq, mdq kag ftqdq?'];// We can break this code by just decrypting with _every_ possible key.const possibilities =tryAll(encryptedMessage);// Most of these decryption attempts aren't readable. Sad.// We can speed up finding the ones that are probably junk with an if checkconst likelyPossibilities = possibilities.filter(function(string){// If isEnglish(string) returns true, filter saves in the output arrayreturnisEnglish(string);})

Since this callback just calls isEnglish, we could actually write it even more concisely, like this:

// Secret message! This was a string encrypted with a key between 1 and 24.const encryptedMessage ='mduqxxq, mdq kag ftqdq?'];// We can break this code by just decrypting with _every_ possible key.const possibilities =tryAll(encryptedMessage);// Most of these decryption attempts aren't readable. Sad.// We can speed up finding the ones that are probably junk with an if checkconst likelyPossibilities = possibilities.filter(isEnglish);

const prices =[12,19,7,209];
prices.reduce(function(totalPrice, nextPrice){// totalPrice is the price so far
console.log(`Total price so far: ${totalPrice}`)
console.log(`Next price to add: ${nextPrice}`)// update totalPrice by adding the next price
totalPrice += nextPrice
// return the total price, and start over againreturn totalPrice
},0)// ^ the second argument to `reduce` is the totalPrice to start with on the first iteration

Like map and filter, reduce accepts a callback, which it runs on each element of the array.

Unlike map and filter, the callback we pass to reduce accepts two arguments: a totalPrice, and a nextPrice.

totalPrice is like total in the first example: It's the price we've gotten by adding up all the prices we've seen so far.

nextPrice is the number we got by doing prices[i] in the first example. Recall that map and reduce automatically index into the array for us, and pass this value to their callbacks automatically. reduce does the same thing, but passes that value as the second argument to its callback.

Just like with map and reduce, on each iteration, we have to return the value we're interested in getting back at the end of the loop. Namely, totalPrice.

Finally, note that we pass another argument after the callback to reduce. In this case, we pass 0. This is analogous to the line in the first example where we set const total = 0. That second argument is the initial value of totalPrice.

reduce is harder to grok than map or filter, so let's take a look at another example.

In the previous example, we used reduce to collect an array of numbers into a sum. But reduce is versatile. We can use it to turn a list into a any single result.

It's alright if this one feels confusing. It's a new idea even for more experienced developers. But, it's worth the work to wrap your head around it: We'll need this little bit of insight to make sense of transduction a little bit later.

...And, trust me, transduction alone is cool enough to be worth the work.