- Published on
Functional programming in JavaScript
- Authors
- Name
- Igor Tosic
Functional programming in JavaScript
Functional programming relies on functions :). And based on mathematical functions.
The idea of FP is the same as in OOP: to make our code clear and understandable, easy to extend, and easy to maintain. It will allow us not to repeat ourselves, keeping our code dry and memory efficient.
In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions without mutating state and data.
In functional programming, functions are treated as first-class citizens, meaning they can be bound to names (including local identifiers), passed as arguments, and returned from other functions, just as any other data type can. That allows programs to be written in a declarative and composable style, where small functions are combined in a modular manner. Functional programming is sometimes synonymous with purely functional programming, a subset of functional programming that treats all functions as deterministic mathematical functions, or pure functions. When a pure function is called with some given arguments, it will always return the same result and cannot be affected by any mutable state or other side effects. This is in contrast with impure procedures, common in imperative programming, which can have side effects (such as modifying the program's state or taking input from a user).
If you want to break things down in functional programming, it all comes down to this concept of pure functions. That is the main difference from OOP concepts such as encapsulation, abstraction, polymorphism and inheritance.
Pure functions
Okay, so what are pure functions!? A Pure Function returns the same result if the same arguments are passed. A function cannot modify anything outside of itself. No side effects.
We are going to see what is that side effect. If we log mutateArr we will get undefined because our function is returning nothing.
const array = [1, 2, 3, 4, 5];
function mutateArr(arr) {
arr.pop()
}
console.log(mutateArr(array));
But if log array
console.log(array)
, we will get [ 1, 2, 3, 4 ]
. So, it means we have mutated our array. And that is a side effect. This function has side effects, and it modifies anything outside of itself. This function has side effects, and it modifies anything outside of itself. We can imagine this array as a global state that can interact with everything in an application and produce many bugs, especially if many functions interact with the same state.
We can fix it like this:
const array = [1, 2, 3, 4, 5];
function mutateArr(arr) {
const newArray = [].concat(array);
newArray.pop();
return newArray;
}
console.log(mutateArr(array));
So, we make a new copy of an array without affecting the outside world. But could it be everything pure function?
Having everything pure without side effects is nice, but programming and applications don't work like that. Our program needs to communicate with the outside world :). To do fetch calls to talk to some other functions, etc. So, our goal is to minimize side effects.
Perfect function should do one task and return some statement. It should be pure :), with no shared state with other functions and an immutable state. We always return a new copy of the output. Functions should be composable, and I will explain this through examples. And most important, we want to make functions predictable.
Imperative vs declarative
We have those concepts in programming. Imperative code is code that tells the machine what to do and how to do it. Declarative code tells it what to do and what should happen. It doesn't say to the computer how to do things. A computer is better at being imperative. That is, it needs to know how to do something. We, on the other hand, as humans, are more declarative. We usually say to friends, hei give that or that, but we don't say how they will do that. Humans are more declarative :). For example, machine code is more imperative when we are talking about computers. If we go level up and up forward to some high-level languages, it becomes more declarative. We declare a variable, and the computer does the rest of the job for us. A good example in JS for imperative vs declarative is for loop: code
for (let i = 0; i < 10; i++){
console.log(1)
}
vs
[1,2,3,4,5].forEach(item => console.log(item))
In the old traditional for loop, you have too many instructions to say what to do. With forEach
you have that more declarative way. jQuery is also a good example of an imperative approach. But I am writing this because functional programming helps us be more declarative. Declarative code is easier to read, and we can be more productive.
Immutability
In functional programming, the idea of immutability is not changing state but instead making copies of the state and returning a new state every time.
HOF and Closure
Functions are first-class citizens, which means we can have high-order functions, and we can also have closures. HOF means it's a function that does one of two things. It either takes one or more functions as arguments. Or returns a function as a result, often called a callback. A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function. In JavaScript, closures are created every time a function is created at function creation time.
Curring
Currying in JavaScript transforms a function with multiple arguments into a nested series of functions, each taking a single argument. Currying helps you avoid passing the same variable multiple times, and it helps you create a higher-order function. An example looks like this:
const sum = (a, b) => a*b
//by now Curring
const curriedSum = (a) => (b) => a*b
curriedSum(5)(4)
Because of closures, we have access to the A variable inside the B function. But why is this useful!?
const curriedSum = (a) => (b) => a*b
const curriedSumBy5 = curriedSum(5)
//after some time, after 7 years
curriedSumBy5(4)
And instead of running curriedSum(5)
this function forever it will be run only once. So when we run curriedSumBy5(4)
we run only this part of function (b) => a*b
.
Memoization
To understand how dynamic programming works, we need to understand what caching means. Caching is a way to store values so you can use them later on. Memorization is a specific form of caching.
function memoizeAddTo50(n) {
let cache = {};
return function(n) {
if (n in cache) {
return cache[n];
} else {
console.log('long time');
const answer = n + 50;
cache[n] = answer;
return answer;
}
}
}
If we try to call this function memoizeAddTo50(5)
with same value more than once we will see caching in action :).
Compose
Compose is the most powerful concept. Function composition is an approach where the result of one function is passed on to the next function, which is passed to another until the final function is executed for the final result. Function compositions can be composed of any number of functions.
In functional programming you can use something called compose. Compose doesn't exist in JavaScript, but we have many libraries that let you use compose. One of the best is ramdajs - https://ramdajs.com/docs/#compose.
Also we can make our own custom compose function, and here you have one of my examples:
fn1(fn2(fn3(50)));
compose(fn1, fn2, fn3)(50) //Right to left
pipe(fn3, fn2, fn1)(50)//left to right
const compose = (f, g) => (a) => f(g(a))
const pipe = (f, g) => (a) => g(f(a))
const multiplyBy2AndAbsolute = compose((num) => num*2, Math.abs)
console.log(multiplyBy3AndMakeAbsolute(-10))
You can also see in this example function with name pipe. Actually, it is the same thing as composing but just going from left to right, https://ramdajs.com/docs/#pipe.
Most of those concepts could be explained more in-depth, but this article is about generally functional programming and things around it. Functional programming is very lovely. The idea in functional programming is to keep functions small, pure, and composable. This idea of immutability is that a function takes inputs and returns outputs to be used with other functions. It allows us to have a predictable program that minimizes bugs because everything is simple.
But is functional programming the answer for everything!? Sometimes OOP is a better way for some cases. That is where we, as programmers, come out on the stage and show our best :)