What’s the Big Deal With Functional JavaScript?
Functional programming patterns are exploding their way into common consciousness as more developers talk about them. However, the ideas behind them are not exactly new.
They’re simply an alternative way to instruct our code to be evaluated.
Functional patterns in JavaScript are a big deal, not because it is intrinsically better than other patterns available, but rather it is because it encourages the person behind the code to leverage existing methods and available techniques that aren’t being used enough.
Which brings us back to the original question: what’s the big deal with functional JavaScript?
First, Let’s Get Back to the Basics
Functional programming runs on the idea that what you put in will always produce the same outcome, regardless of order.
For example, regardless of how you add or subtract the following numbers, the result will always remain the same.
3 + 8 - 5 = 6
8 + 3 - 5 = 6
8 - 5 + 3 = 6
In JavaScript, this can be achieved through functions constructed in a way that isn’t dependent on state or process-based transformations. This means no loops, no nesting — nothing that requires a particular process to create an output.
Functional programming is like making a loaf of ciabatta — it doesn’t matter what order you put the ingredients in (flour, water, yeast, and salt). As long as they’re all there, in the right quantities and mixed together, you can’t really mess it up. Trust me, I’ve tried.
Functional patterns and ciabatta loaves are two of the few fail-safe things in life!
First-Class Baby!
The special thing about JavaScript is that it allows for first class functions. What this means is that functions can be treated as data and assigned to variables.
For example, the following syntax is acceptable in JavaScript:
const cat = Cat();
const makeCatJump = jump(cat);
What’s happened here is that the Cat()
function is assigned to the variable cat
and the generated output is treated like a value, which is then passed into the function jump()
.
This is possible because JavaScript allows for a thing called higher order functions. What that means is that a function can take in another function and return a function.
Confused yet?
It may sound complicated but it’s actually more like substitution in mathematics.
Here’s a diagram to help make it easier to mentally digest.
So where exactly would you use this? Anything that requires functions as a substitution stand-in like async flow controls with callback functions. It’ll save you from writing how to deal with the async results over and over again, in addition to making your code much more readable.
No Mutants Here
Well, that’s a bit of an exaggeration. When it comes to functional patterns, mutable objects are never part of the equation. This is because data becomes lossy.
What does lossy mean?
Lossy is a concept in which data is discarded and has the potential to change — in other words, it is mutable. Immutability creates a certain permanence when traveling through the functional pattern. If we look at the mathematical example given earlier, we don’t expect the 8
to suddenly morph into a different number. The same concept is applied to functional programming.
It’s important to note that using const
to create immutability is a bit of a misunderstanding. While you can’t change the assigned value to a const
, you can still add to it, which means that const
is actually mutable.
True immutability means that you can’t change or make additions to it at all, otherwise the value becomes mutable.
In JavaScript, you can enforce a certain level of immutability using the freeze()
method.
For example:
const catDetails = Object.freeze({
name: 'Tibbers',
age: 3,
color: 'Tuxedo'
});
catDetails.weight = '3 pounds';
//Error: Cannot assign to read only property 'weight' of object Object
However, this method only works on the surface and applies to the first level of objects only.
For example, the following is a mutable object:
const catDetails = Object.freeze({
name: 'Tibbers',
age: 3,
color: 'Tuxedo',
greeting: {morning: 'Meow', hungry: 'Meow meow'}
});
catDetails.greeting.night = 'Purrrrr';
console.log(catDetails.greeting);
What’s the Big Deal With Immutability?
At some point, your data is going to need to change. Immutability isn’t exactly about the values assigned but rather the shape of the thing you are working with.
In the catDetails
objects above, the first example is immutable because freeze()
prevents it from changing in shape through the addition of weight
. In the second example, the shape is mutated by the addition of catDetails.greeting.night
The big deal with immutability and its hard rule in functional patterns is that it ensures a level of predictability. Imagine you’re receiving a cart object but find yourself in a bit of a pickle because, somewhere along the lines, your cart items’ expected data shape have changed.
When your expected values are immutability, it also allows you to create references and value equality with hidden side effects. Testing becomes easier because you know exactly what to expect for inputs and outputs. With functional patterns, there are no contingencies or ambiguity to deal with.
Final Thoughts
Thinking in a functional manner for your JavaScript code can help reduce future headaches as you try and trace where things go wrong.
Understanding how functional patterns operate can also de-codify your code by making it more straightforward. The long term stability of your code is not resting against the potential of change. Rather, it just does the job it was designed to do — giving your code a level of vaccination against technical debt.
Code reviews and updates will generally only occur when mutations in the data shape occur. Only then will your code need to adapt to the new environments.