Before going to it’s principles, let first understand a bit about it.
What is Functional Programming
Functional programming is a paradigm which has its roots in mathematics, primarily stemming from lambda calculus. Its main focus is on “what to solve” in contrast to an imperative style where the main focus is “how to solve”. Functional programming aims to be declarative and treats applications as the result of pure functions which are composed with one another.
The primary aim of this style of programming is to avoid the problems that come with shared state, mutable data and side effects which are common place in object oriented programming. Functional programming tends to be more predictable and easier to test than object oriented programming but can also seem dense and difficult to learn for new comers but functional programming isn’t as difficult as it would at first seem.
Why do we need Functional Programming?
There are a few major benefits to Functional Programming and minor drawbacks:
Advantages:
- More readability, thus maintainability
- Less buggy, especially in concurrent contexts
- A new way of thinking about problem solving
- (Personal bonus) Just great to learn about!
Drawbacks:
- Can have performance issues
- Less intuitive to work with when dealing with state and I/O
- Unfamiliar for most people + math terminology that slows the learning process
Functional programming tends to be more predictable and easier to test than object oriented programming.
Principles
On broader level, there mainly 6 principles of Functional Programming
- Purity
- Immutability
- Disciplined state
- First class functions and high order functions
- Type systems
- Referential transparency
Lets see them in details below.
Purity
- A Pure Function is a function (a block of code) that always returns the same result if the same arguments are passed.
- It does not depend on any state or data change during a program’s execution. Rather, it only depends on its input arguments.
- Also, a pure function does not produce any observable side effects such as network requests or data mutation, etc.
- It has no side effects like modifying an argument or global variable or outputting something.
- Pure functions are predictable and reliable. Most of all, they only calculate their result. The only result of calling a pure function is the return value.
Lets see an example below for calculating GST.
const calculateGST = (productPrice) => {
return productPrice * 0.05;
}
- The computation in square function depends on the inputs. No matter how many times we call a square function with the same input, it will always return the same output.
Example below
const sqaureIt= (number) => {
return number * number;
}
Immutability
- Immutable data cannot change its structure or the data in it.
- Once you assign a value to something, that value won’t change.
Immutability is at the core of functional programming. This eliminates side effects (anything outside of the local function scope), for instance, changing other variables outside the function.
- Immutability helps to maintain state throughout the runtime of a program.
- It makes code simple, testable, and able to run on distributed and multi-threaded systems
- Since function has a disciplined state and does not change other variables outside of the function, we don’t need to look at the code outside the function definition.
- Immutability comes into play frequently when we work with data structures. Many array methods in JavaScript directly modify the array.
For example, .pop()
directly removes an item from the end of the array and .splice()
allows you to take a section of an array.
// We are mutating myArr directly
const myArr = [1, 2, 3];
myArr.pop();
// [1, 2]
Instead, within the functional paradigm, we would copy the array and in that process remove the element we are looking to eliminate.
// We are copying the array without the last element and storing it to a variable
let myArr = [1, 2, 3];
let myNewArr = myArr.slice(0, 2);
// [1, 2]
console.log(myArr);
Disciplined state
- Disciplined state is the opposite of shared, mutable state.
- A shared mutable state is hard to keep correct since there are many functions that have direct access to this state. It is also hard to read and maintain.
- With mutable state, we need to look up for all the functions that use shared variables, in order to understand the logic. - It’s hard to debug for the very same reason.
- When we are coding with functional programming principles in mind, you avoid, as much as possible, having a shared mutable state.
- Of course we can have state, but you should keep it local, which means inside our function. This is the state discipline : we use state, but in a very disciplined way.
An example of the drawbacks of shared, mutable state would be the following:
const logElements = (arr) => {
while (arr.length > 0) {
console.log(arr.shift());
}
}
const main = () => {
const arr = ['Node', 'React', 'Javascript', 'Typescript'];
console.log('=== Before sorting ===');
logElements(arr);
arr.sort();
console.log('=== After sorting ===');
logElements(arr);
}
main();
// === Before sorting ===
// "Node"
// "React"
// "Javascript"
// "Typescript"
// === After sorting ===
// undefined
We can see that the second call produces no result since the first call emptied the input array and thus mutated the application state generating an unexpected output.
To fix this we turn to immutability and the use of copies to keep the initial state transparent and immutable.
const logElements = (arr) => {
while (arr.length > 0) {
console.log(arr.shift());
}
}
const main = () => {
const arr = ['Node', 'React', 'Javascript', 'Typescript'];
console.log('=== Before sorting ===');
logElements([...arr]); // Change in line
const sorted = [...arr].sort(); // Assign sorted items to another variable
console.log('=== After sorting ===');
logElements([...sorted]); // Change in line
}
main();
// === Before sorting ===
// "Node"
// "React"
// "Javascript"
// "Typescript"
// === After sorting ===
// "Javascript"
// "Node"
// "React"
// "Typescript"
First class functions and high order functions
Higher order functions are functions which do at least one of the following:
- Takes one or more functions as arguments
- Returns a function as its result
Functional programming treats functions as first-class citizens.
This means that functions are able to be passed as arguments to other functions, returned as values from other functions, stored in data structures and assigned to variables.
const plusFive = (number) => {
return number + 5;
};
// f is assigned the value of plusFive
let f = plusFive;
plusFive(3); // 8
// Since f has a function value, it can be invoked.
f(9); // 14
Higher order function is the function that takes one or more functions as arguments or returns a function as its result.
Type systems
By using types we leverage a compiler to help us avoid common mistakes and errors that can occur during the development process.
With JavaScript we could do the following:
const add = (left, right) => {
return left + right;
}
add(2, 3) // 5
add(2, "3"); // "5"
This is bad because now we are getting an unexpected output which could have been caught by a compiler.
Let’s look at the same code written with Typescript:
const add = (left: number, right: number): number => {
return left + right;
}
add(2, 3) // 5
add(2, "3"); // error!
Here we can see the compiler in action protecting us from such basic issues, of course much more is possible with a statically typed approach to developing but this should give you the gist of why it is useful to use.
Referential transparency
Referential transparency is a fancy way of saying that if you were to replace a function call with its return value, the behaviour of the programme would be as predictable as before.
Referentially transparent functions only rely on their inputs and thus are closely aligned with pure functions and the concept of immutability.
An expression is said to be referentially transparent if it can be replaced with its corresponding value without changing the program’s behaviour.
To achieve referential transparency, a function must be pure. This has benefits in terms of readability and speed. Compilers are often able to optimise code that exhibits referential transparency.
const two = () => {
return 2;
}
const four = two() + two(); // 4
// or
const four = two() + 2; // 4
// or
const four = 2 + two(); // 4
// or
const four = 2 + 2; // 4
Conclusions
Functional programming gives us some principles which make our code more readable, predictable and testable.
This allows us to have code which contains less bugs, easier onboarding and a generally nicer codebase from my experience.
Also, to be notified about my new articles and stories: Follow me on Medium.
Subscribe to my YouTube Channel for educational content on similar topics
Follow me on Medium and GitHub for connecting quickly.
You can find me on LinkedIn as it’s a professional network for people like me and you.
Cheers!!!!