So what is this Functional Programming thing?
Functional programming is the process of building software by composing pure functions, avoiding shared state, mutable data, and side-effects. Functional programming is declarative (what not how) rather than imperative (what + how), and application state flows through pure functions. Contrast with object oriented programming, where application state is usually shared and co-located with methods in objects.
Functional programming is a programming paradigm, meaning that it is a way of thinking about software construction based on some fundamental, defining principles. Other examples of programming paradigms include object oriented programming and procedural programming.
Why should we use it?
Functional programming with referentially transparent expressions shows a different way of thinking about the programs, instead of objects and values changing over time — have small composable functions yielding the same output for the same input. It gives the ability to better understand and reason about the code, know exactly what function does just by looking at it’s signature, be more disciplined about program responsibilities and come up with better design.
+
Functional code tends to be more concise, more predictable, and easier to test than imperative or object oriented code.
+
FP lies at the heart of both of the dominant frameworks, React (avoiding shared mutable DOM is the motivation for its architecture and 1-way data flow) and Angular.
Core concepts of functional programming:
- Pure functions
- Function composition
- Avoid shared state
- Avoid mutating state
- Avoid side effects
What do we mean by pure functions?
A pure function is a function which:
- Given the same inputs, always returns the same output, and
- Has no side-effects
A longer definition of a pure function is:
- The function always returns the same result if the same arguments are passed in. It does not depend on any state, or data change during a program’s execution. It must only depend on its input arguments.
- The function does not produce any observable side effects such as network requests, input and output devices, or data mutation.
Pure functions have lots of properties that are important in functional programming, including referential transparency (you can replace a function call with it’s resulting value without changing the meaning of the program).
Referential what?
All pure functions are necessarily referentially transparent. Since, by definition, they cannot access anything other than what they are passed, their result must be fully determined by their arguments, which means that given a function and an input value, you will always receive the same output. That is to say there is no external state used in the function.
Here is an example of a referentially transparent (pure) function:
function priceAfterTax(productPrice) {
return (productPrice * 0.20) + productPrice;
}
With a referential transparent function, given an input and a function, you could replace it with a value instead of calling the function. So instead of calling priceAfterTax
with a parameter of 100, we could just replace that with 120.
******
In contrast there is the concept of referential opaqueness. This means the opposite. Calling the function may not always produce the same output.
let tax = 20;function calculateTax(productPrice) {
return (productPrice * (tax/100)) + productPrice;
}
So that's the same as an impure function? Looks like.
Where do we need pure functions?
Pure functions are used heavily in Functional Programming. And, libraries such as ReactJS and Redux require the use of pure functions.
But, pure functions can also be used in regular JavaScript that doesn’t depend on a single programming paradigm. You can mix pure and impure functions and that’s perfectly fine.
Not all functions need to be or should be pure. For example, an event handler for a button press that manipulates the DOM is not a good candidate for a pure function. But the event handler can call other pure functions which will reduce the number of impure functions in your application.
+
They also make maintaining and refactoring code much easier. You can change a pure function and not have to worry about unintended side effects messing up the entire application and ending up in debugging hell.
When used correctly the use of pure functions produces better quality code. It’s a cleaner way of working with lots of benefits.
Function composition
… is the process of combining two or more functions in order to produce a new function or perform some computation.
The function that has other functions inside is called a higher-order function, a function that is inside another function is called a callback.
A higher-order function is a function that can take another function as an argument, or that returns a function as a result.
… and the most popular ones are: filter
, map
, reduce
.
If you change the order of the composition, the output will change. Order of operations still matters. f(g(x))
is not always equal to g(f(x))
, but what doesn’t matter anymore is what happens to variables outside the function.
With impure functions, it’s impossible to fully understand what a function does unless you know the entire history of every variable that the function uses or affects.
Remove function call timing dependency, and you eliminate an entire class of potential bugs.
Side Effects
A side effect is any application state change that is observable outside the called function other than its return value. Side effects include:
- Modifying any external variable or object property (e.g., a global variable, or a variable in the parent function scope chain)
- Printing to a screen or console
- Mutating data
- Making a HTTP request
- Triggering any external process
- Calling any other functions with side-effects
- DOM Query/Manipulation
- Math.random()
- Getting the current time
Side effects are mostly avoided in functional programming, which makes the effects of a program much easier to understand, and much easier to test.
What you do need to know right now is that side-effect actions need to be isolated from the rest of your software. If you keep your side effects separate from the rest of your program logic, your software will be much easier to extend, refactor, debug, test, and maintain.
This is the reason that most front-end frameworks encourage users to manage state and component rendering in separate, loosely coupled modules.
Avoid mutating state
To avoid mutating arrays and objects we are working with, we need to make a copy (which points to a different place in memory) that we then modify.
We basically need to go non-destructive here.
Operations that modify the original collection are destructive, and those that leave the original collection intact are non-destructive.
Destructive methods that would modify the original Array
are:
.push(), .pop(), .shift(), .unshift(), .splice()
Mutating the original Array
isn't necessarily a bad thing, but there's also a way to add elements nondestructively, so why not.
In order to be non-destructive we need to use methods that produce a new object (I meant to say Array
, but hey array is an Object
) rather than mutating.
Here are a few ways to do that:
.slice()
2. spread oprerator
3. slice + spread operator
3. Object.assign()
Let's take a look at each one of them:
1. slice()
with no arguments:
If we don’t provide any arguments, .slice()
will return a copy of the original Array
with all elements intact:
const nums = [5, 7, 9, 11, 10]const copyNums = nums.slice()
copyNums// => [5, 7, 9, 11, 10]
slice()
is non-destructive since it returns a new copy and it doesn’t change the content of input array. It
is normally used in FP to avoid side-effects.
Array.prototype.slice()
Note that he Array
returned by .slice()
has the same elements as the original, but it's a copy — the two Array
s point to different objects in memory. If you add an element to one of the Array
s, it does not get added to the other.
with arguments:
.slice()
method returns a portion, or slice, of an Array
, arguments help us specify which slice we are after.
We can provide two arguments to .slice()
, the index where the slice should begin and the index before which it should end:
const days = [‘Mon’, ‘Tue’, ‘Wed’, ‘Thu’, ‘Fri’, ‘Sat’, ‘Sun’];days.slice(2, 5)// => [“Wed”, “Thu”, “Fri”]
This code would return the whole array without the last element:
days.slice(0, -1)
If no second argument is provided, the slice will run from the index specified by the first argument to the end of the Array
:
const days = [‘Mon’, ‘Tue’, ‘Wed’, ‘Thu’, ‘Fri’, ‘Sat’, ‘Sun’]days.slice(5)// => [“Sat”, “Sun”]days.slice(-2)// => [“Sat”, “Sun”]
To remove the first element and return a new Array
, we call .slice(1)
which is means that we want a copy of the array starting from index 1.
2. Spread operator
Spread operator ...
allows us to spread out the contents of an existing Array
into a new Array
.
const words = [‘esoteric’, ‘ubiquitous’, 'discernment']const newWords = […words]words// => [‘esoteric’, ‘ubiquitous’, 'discernment']newWords// => [‘esoteric’, ‘ubiquitous’, 'discernment']
…and we can also use it if we want to add new elements but preserve the original Array
:
const newWords = […words, 'inclination']newWords// => [‘esoteric’, ‘ubiquitous’, 'discernment', 'inclination']
3. .slice() + spread operator
The non-destructive friends. The type of friends, I mean, operators we want to be around.
Combining .slice()
and the spread operator allows us to replace elements non-destructively, leaving the original Array
unharmed:
const words= [‘ambiguous’, ‘eloquent’, ‘transcending’, ‘adamant’, ‘subliminal aversion’]const newWords = […words.slice(0, 1), ‘excruciating’, ‘reciprocal’, ‘adjacent’, …words.slice(3)]…words.slice(0, 1) — at index 0 take 1 element…words.slice(3) — take all from index 3 until the end of the arrayNOTE: that index 1 and index 2 will be missing in the newWords.words// => [‘ambiguous’, ‘eloquent’, ‘transcending’, ‘adamant’, ‘subliminal aversion’]newWords// => ["ambiguous", "excruciating", "reciprocal", "adjacent", "adamant", "subliminal aversion"]
If we didn’t use spread operator here it would result to a nested array.
4. Object.assign()
Object.assign() method copies values of all enumerable own properties from one object to another. For example:
var x = {name: "Lapinas"}var y = Object.assign({}, x)y // => {name: "Lapinas"}
But be aware that nested objects are still copied as reference.
In the example above, we use Object.assign()
and pass in an empty object as the first parameter to copy the properties of x
instead of mutating it in place. In this case, it would have been equivalent to simply create a new object from scratch, without Object.assign()
, but this is a common pattern in JavaScript to create copies of existing state instead of using mutations.
So what are the destructive method examples then?
We can look at splice()
— it’s a very destructive operator.
The documentation shows three ways to use .splice()
:
array.splice(start)array.splice(start, deleteCount)array.splice(start, deleteCount, item1, item2, …)
1. WITH A SINGLE ARGUMENT:
array.splice(start)
The first argument expected by splice()
is the index at which to begin the splice. If we only provide the one argument, splice()
will destructively remove a chunk of the original Array
beginning at the provided index and continuing to the end of the Array
.
***************************************************************
SPLICE vs SLICE:
splice(-2)
will work the same as slice(-2)
, except that it will destroy the original array (I mean, modify). Slice
will return a new array with the origianl unchanged.
***************************************************************
2. WITH TWO ARGUMENTS:
array.splice(start, deleteCount)
When we provide two arguments to .splice()
, the first is still the index at which to begin splicing, and the second dictates how many elements we want to remove from the Array
. For example, to remove 3
elements, starting with the element at index 2
:
const days = [‘Mon’, ‘Tue’, ‘Wed’, ‘Thu’, ‘Fri’, ‘Sat’, ‘Sun’]days.splice(2, 3)// => [“Wed”, “Thu”, “Fri”]BUT DAYS ARE NOW://=> ["Mon", "Tue", "Sat", "Sun"] !!!!!!!!!
3. WITH 3+ ARGUMENTS:
array.splice(start, deleteCount, item1, item2, …)
array.splice(startWhere, deleteHowManyThere, theseAreGoingThereInstead)
After the first two, every additional argument passed to .splice()
will be inserted into the Array
at the position indicated by the first argument:
const cards = [‘Ace of Spades’, ‘Jack of Clubs’, ‘Nine of Clubs’, ‘Nine of Diamonds’, ‘Three of Hearts’]cards.splice(2, 1, ‘Ace of Clubs’)// => [“Nine of Clubs”] //this gets deleted, 'Ace of Clubs' is added at index 2cards// => [“Ace of Spades”, “Jack of Clubs”, “Ace of Clubs”, “Nine of Diamonds”, “Three of Hearts”]
When second argument is 0:
cards.splice(2, 0, ‘Ace of Clubs’)//nothing is deleted, 'Ace of Clubs' is added at index 2
// => []cards//=> ["Ace of Spades", "Jack of Clubs", "Ace of Clubs", "Nine of Clubs", "Nine of Diamonds", "Three of Hearts"]
Notice that splice()
returns an empty Array
when we provide a second argument of 0
. This makes sense because the return value is the set of elements that were removed, and we're telling it to remove 0
elements.
Some summary here, please.
The only way we’ll be able to keep up with the exponentially increasing complexity will be to decrease the complexity of understanding programs. To maintain the goliath apps of tomorrow, we must learn to build more expressive code. We must learn to write programs that are easier to reason about, debug, create, analyze, and understand. (Eric Elliott)
And that's where the FP comes in.
List of all awesome blog posts I learned these concepts from:
Master the JavaScript Interview: What is Functional Programming? by Eric Elliott
The Two Pillars of JavaScript — Pt 2: Functional Programming. How to Stop Micromanaging Everything by Eric Elliott as well
JavaScript: What Are Pure Functions And Why Use Them? by James Jeffery
Chapter 3: Pure Happiness with Pure Functions
Notes for the next blog:
I wanted to cover map, filter and reduce (higher order functions, which also are non-destructive (returns a new array)).
Thanks for reading!
“When one door closes, you should keep it closed, because the AC is on.”