Writing Clean JavaScript with Functional Paradigms
So I said to him, "That's not a programming language, that's JavaScript"
JavaScript is often the butt of the joke when programmers gather around the watering hole or are sharing war stories; however, JavaScript actually contains a great deal of modern paradigms for writing clean, functional code. In this article, we'll be exploring a few small, but useful functional code blocks that can help improve your code's readability and efficiency.
A Brief Introduction to Functional Programming
If you're unfamilar with functional programming, it's essentially a simplistic way of programming where blocks of code are split into stateless functions. These functions are similar to mathematical functions that are proven to work on certain languages (A language is a finite sets of elements, I.E. a-zA-Z or 0-9). The functions are stateless, meaning no external variables can affect the outcome of the function. If you feed a function a certain input, its output will always be the same. This is extremely useful as it helps eliminate side effects that can cause programmers headaches for days. There are a few languages where this is the only paradigm for writing code (mainly Haskell). Luckily (or unluckily), JavaScript supports it rather than necessitates it and we can use it to our advantage, instead of struggling with it (entirely stateless programming is HARD).
Basic Introduction
Good Scoping
Let's (Wink Wink) start with a properly scoped (let) statement in JavaScript. If you're familiar programming with a language like C or Java, you know that variables exist only within their context or scope. For example, this block of code in Java:
if(true) {
int b = 3;
}
System.out.println(b);
would result in an error, as the print statement referencing b is outside the scope of it's definition. Unfortunately, this similar code in JavaScript:
if(true) {
var b = 3;
}
console.log(b);
will execute just fine. This might seem like a nice feature for about one second, until you realize the implications of having variables scoped like this. In a large function, having duplicate variable names is a very easy issue to run into. JavaScript will let you do this, which can lead to extremely confusing logical problems thanks to their especially lazy type system. The solution: the beautifully scoped LET statement. So, if you code like this:
if(true) {
let b = 3;
}
console.log(b);
it will give you a nice graceful error message letting you know b was not defined. This is a nicer feature of ES6 which will hopefully start becoming more prevalent.
Immutable Variables
Immutable variables are nothing new for standard programming languages, but they can really make a world of difference. As for ES6, JavaScript has added the const keyword, indicating a constant variable that should not be changed. This is incredibly useful, especially in situations where you're iterating over a set of data. For example:
for(var i=0; i < dataset.length; i++) {
if(dataset[i].temperature = 5) {
console.log("Temperature is 5");
}
}
is one of my favorite logic errors in programming. A quick glance at the code will give you the idea that someone is looping through a dataset, checking if the temperature is 5, and then printing to stdout if it is. The best part is that it gleefully compiles and runs without an error message. Unfortunately, it is extremely wrong because the if statement is using an assignment operator instead of a comparison operator. Not only will every item in the dataset tell stdout that it's temperature is 5 (due to javascript truthiness), but also the for loop will actually overwrite every piece in memory to be 5. This, by the way, is a famous exploit that someone tried to implement into the linux kernal in 2003 (see https://freedom-to-tinker.com/2013/10/09/the-linux-backdoor-attempt-of-2003/).
There is an incredibly simple solution to this problem and that's using a constant iterator like this:
for(const temp of dataset) {
if(temp.temperature = 5) {
console.log("Temperature is 5");
}
}
This will throw you a nice big
Uncaught TypeError: Assignment to constant variable.
which is just about as graceful a way of failing as you can get.
Currying Functions
Now we can get into some of the real functional blocks. Currying, (which gets its name from Haskell Curry) allows for almost stateful functions. Although stateful functions might seem like an anti-pattern, they can be extremely useful when it comes to reusable code. Here's the elementary example:
var test = function(word1) {
return function(word2) {
console.log(word1 + ", " + word2);
}
}
//let's start with Hello
var hello = test("Hello");
hello("World");
hello("Universe!");
Which will output
> "Hello, World"
> "Hello, Universe!"
This is can be very useful for doing something like stringing comparisons in a for loop. So this block, with complicated object access and string comparisons, can be easily simplified from:
for(var i=0; i < arr.length; i++) {
for(var j=0; j < arr.length; j++) {
if(arr[i].firstName.toLowerCase().localeCompare(arr[j].firstName.toLowerCase())) {
console.log("This name comes first", arr[i].firstName);
}
}
}
to
stringComparer = function(item1) {
return function(item2) {
return item1.firstName.toLowerCase().localCompare(item2.firstName.toLowerCase();
}
}
for(const item1 of arr) {
stringComparer(item1);
for(const item2 of arr) {
if(stringComparer(item2) {
console.log("This name comes first", item1.firstName);
}
}
}
We can further generify this function by passing a key, allowing for any comparison with any array of objects, like so:
comparison = function(item1, key1) {
return function(item2,key2) {
return item1[key1].toLowerCase().localCompare(item2[key2].toLowerCase());
}
}
}
//comparing first names
for(const item1 of arr) {
stringComparer(item1,"firstName");
for(const item2 of arr) {
if(stringComparer(item2,"firstName") {
console.log("This name comes first", item1.firstName);
}
}
}
//comparing last names
for(const item1 of arr) {
stringComparer(item1,"lastName");
for(const item2 of arr) {
if(stringComparer(item2,"lastName") {
console.log("This name comes first", item1.lastName);
}
}
}
Mapping
Map, a gift from the functional gods of programming. A map function will take any function and map it alongside your array. Using Array.prototype.map, we can pass in any function to apply it throughout an array. Let's use the classic factorial example:
//basic slow recursive factorial function
function fact(a) {
if(a === 0 || a === 1)
return 1;
return fact(a-1) * a;
}
var array = [1,2,3,4];
var factorialArray = [];
for(var i=0; i < array.length; i++) {
factorialArray.push(fact(array[i]);
}
console.log(factorialArray);
If we use map, we get a much cleaner code.
//basic slow recursive factorial function
function fact(a) {
if(a === 0 || a === 1)
return 1;
return fact(a-1) * a;
}
var array = [1,2,3,4];
var factorialArray = array.map(fact);
console.log(factorialArray);
This displays a fast, prettier, and functional way of running a function. We can even write it all as a single line of code, if we pass the function anonymously (not that you should, as it gets a little verbose, but here's the example):
console.log([1,2,3,4].map(function fact(a){if(a === 0 || a === 1){return 1;}return fact(a-1) * a;}));
Reduce
Reduce is a great function when you're in need of an accumulator. Using Array.prototype.Reduce, reduce will run against every element of an array, while building an accumulator based on the function passed in.
Let's say you have a list of ages in a group and you want to find the average. Usually, you'd probably write something like this
var ages = [10, 53, 34, 32,12,53, 75, 23, 34, 43, 33, 22, 11, 64, 22, 34, 53];
var sumOfAges = 0;
for(var i=0; i < ages.length; i++) {
sumOfAge += ages[i];
}
var average = sumOfAges / ages.length;
console.log(average);
Using the Array.reduce function, we can see it becomes a lot cleaner
var ages = [10, 53, 34, 32,12,53, 75, 23, 34, 43, 33, 22, 11, 64, 22, 34, 53];
function sum(a,b){return a+b;}
var sumOfAges = ages.reduce(sum);
var average = sumOfAges / ages.length;
console.log(averages);
Or again with one line
console.log(ages.reduce(function(a,b){return a+b;}) / ages.length );
Final Word
These are just a few of the functional blocks available in JavaScript that functional programming languages use. If you're interested in the subject, I'd highly recommend the book Learn You a Haskell for Great Good!, which provides an introduction to the Haskell language. Functional programming is an extremely interesting and growing field, so hopefully some of this material will assist you in writing cleaner, more concise code.