Dkatalis
Published in

Dkatalis

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:

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.

  1. Let make arguments immutable:
Immutable function
{ 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

Pure function
104
104
104
104

Concepts:

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

A pure function is a function that has the following properties:

  1. 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).
  2. 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.

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.

Immutable DS examples

pure functions + immutable data = referential transparency

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 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;

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

composition: building a logger function

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.

  1. Don’t mutate data
  2. 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!