Functional Programming in JS: Avoiding side affects
--
This document talks about ways to avoid side affects using some functional programming concepts and their benefits.
Content:
· Understanding side affects:
∘ Consider below examples
∘ Fixing the side affects
· Concepts:
∘ What is functional programming?
∘ Pure function:
∘ Immutability
∘ Higher-order Functions
∘ Currying
∘ Composition
∘ The Tenets of Functional Programming (not limited to)
Understanding side affects:
Consider below examples
Lets predict the value of foo
{ ONE: 2, TWO: 4 }
{ ONE: 6, TWO: 12 }
{ ONE: 24, TWO: 48 }
foo: { ONE: 24, TWO: 48 }
The value of foo if no longer { 'ONE': 1, 'TWO': 2}
after calling mapMultiplier
function.
Why does this happen?
In JS objects assignment are reference to the original object. Meaning, params
and foo
are same objects. params
is just pointing to the address of foo
because of Prototype Chaining.
// More exampleconst a = { 'random': 1 }
const b = a;
b.random = 2;console.log(a, b); // { 'random': 2} { 'random': 2}
console.log(a === b); // true
Another example:
assert.js:386
throw err;
^AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:assert(getSavingsAmount(100) === 104)
getSavingsAmount
functions returns different value for same inputs. Hence assertion fails. getSavingsAmount
is relying on some variable to which it doesn’t have control over.
Our code shouldn’t be changing its behaviour by somebody else’s unintended changes.
Let’s see how functional programming concepts helps us solve theses above issues among others.
Fixing the side affects
- Let make arguments immutable:
{ ONE: 2, TWO: 4 }
{ ONE: 3, TWO: 6 }
{ ONE: 4, TWO: 8 }
foo: { ONE: 1, TWO: 2 }
Here, we aren’t meddling with params
argument, instead we are cloning the params using …
operator.
Does that mean I need to clone all arguments for every functions?
Nope. This applies to only non primitives data types
2. Lets make the getSavingsAmount
Pure function
104
104
104
104
Concepts:
What is functional programming?
Functional programming is a programming paradigm — a style of building the structure and elements of computer programs — that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data — Wikipedia
Pure function:
A pure function is a function that has the following properties:
- For same arguments always returns same value(no variation with local static variables, non-local variables, mutable reference arguments or input streams from I/O devices).
- Its evaluation has no side effects (no mutation of local static variables, non-local variables, mutable reference arguments or I/O streams).
examples:
// Impure functions
Math.random()
Date.now()// Pure functions
Math.sqrt(100)
Math.pow(2,2)
One of the best things about Pure Functions are the ease of testability.
Immutability
No mutation of any functional arguments, global objects to avoid any side affects.
Below are the ways to achieve immutability for different Data Structures in JS.
pure functions + immutable data = referential transparency
Higher-order Functions
Higher-order functions are functions that accept a function as an argument and return a function. They are used to add to the functionality of a function.
In the above example, we create a withLog
higher-order function that takes a function and returns a function that logs a message before the wrapped function runs.
const add = (a, b) => a + b;
const addWithLogging = withLog(add);
addWithLogging(3, 4);
// calling add
// 7
withLog
HOF can be used with other functions as well and it works without any conflicts or writing extra code. This is the beauty of a HOF.
const addWithLogging = withLog(add);
const hype = s => s + '!!!';
const hypeWithLogging = withLog(hype);
hypeWithLogging('Sale');
// calling hype
// Sale!!!
One can also call it without defining a combining function.
withLog(hype)('Sale');
// calling hype
// Sale!!!
Uses functions as first class objects
Currying
Currying means breaking down a function that takes multiple arguments into one or multiple levels of higher-order functions.
Let’s take the add
function.
const add = (a, b) => a + b;
When we are to curry it, we rewrite it distributing arguments into multiple levels as follows.
const add = a => {
return b => {
return a + b;
};
};
add(3)(4);
// 7
The benefit of currying is memoization. We can now memoize certain arguments in a function call so that they can be reused later without duplication and re-computation.
// assume getOffsetNumer() call is expensive
const addOffset = add(getOffsetNumber());addOffset(4); // 4 + getOffsetNumber()
addOffset(6); // 6+ getOffsetNumber()
This can be better than using both arguments everywhere?
add(4, getOffsetNumber());
add(6, getOffsetNumber());
add(10, getOffsetNumber());
We can also reformat our curried function to look succinct. This is because each level of the currying function call is a single line return statement. Therefore, we can use arrow functions in ES6 to refactor it as follows.
const add = a => b => a + b;
Composition
In mathematics, composition is defined as passing the output of one function into input of another so as to create a combined output. Here, Composition is about creating small functions and creating bigger and more complete functions with them
Above example builds a log
function using multiple level of composition.
Composition goes follows:
1. loggerWithTimeStamp() --> getSimpleText() and getCurrentDateTime()2. loggerWithTimeStampAndLevel() --> loggerWithTimeStamp()3. loggerWithTimeStampAndLevelAndCallee() --> loggerWithTimeStampAndLevel()
Lets run,
Task: run [ INFO ] 2021-02-09T12:09:13.820Z: this is logger statement
Task: run [ WARN ] 2021-02-09T12:09:13.826Z: something to log
Task: run [ ERROR ] 2021-02-09T12:09:13.828Z: this is logger statement
Task: run [ ERROR ] 2021-02-09T12:09:13.828Z: this is logger statement
Task: run [ ERROR ] 2021-02-09T12:09:13.828Z: this is logger statement
Benefit: With this we have smaller, testable, reusable functions which can be used to plug, play and compose bigger functions based on requirements.
The Tenets of Functional Programming (not limited to)
- Don’t mutate data
- Use pure functions: fixed output for fixed inputs, and no side effects
That’s all folks. Here we understood some important concepts of functional programming and their benefits.
Check out other functional programming benefits and concepts like piping, declarative programming approach over imperative, etc
Like to discuss this with like-minded engineers? You might just find your new team! Join us in making the next life-centric digital solutions!