The Yin and Yang of JavaScript: Exploring OOP and Functional Programming II

Erkand Imeri
Just Eat Takeaway-tech
12 min readAug 11, 2023
https://codepen.io/erkand-imeri/pen/zYMxrrG

On to Functional Programming

Welcome back to the second part of the article. In the previous part, we delved deep into the world of Object Oriented Programming in JavaScript, exploring the fundamentals and its strength on how it makes JavaScript code more structured. But as we acknowledged, OOP is just half of the story.

Today, we’ll explore the other side of the story: functional programming. Functional Programming represents the yang to OOP’s yin in JavaScript. A contrasting yet complementary approach. We’ll explore the core principles, efficient and reliable ways to write code.

In computer programming, a function or subroutine is a sequence of program instructions that performs a specific task, packaged as a unit. This unit can then be used in programs wherever that particular task should be performed.

A function takes some inputs which we call arguments/parameters and produces some output called return values. But simply wrapping our code inside functions doesn’t mean we are working in the functional programming paradigm. Before we can say that, there are some core concepts to which we need to adhere.

Purity, Immutability, and First-Class Functions

One of those core concepts of functional programming is the concept of pure functions.

A pure function would be one where given the same input, it will return the same output ( no randomness will be involved ), and a function is pure if it doesn’t modify any variable/state declared outside the scope of the function. For example:

function squareNumber(num) {
return num * num;
}

console.log(squareNumber(2)); // Outputs: 4
console.log(squareNumber(2)); // Outputs: 4
console.log(squareNumber(4)); // Outputs: 16
console.log(squareNumber(5)); // Outputs: 25
console.log(squareNumber(5)); // Outputs: 25

The above function squareNumber is a good example of a pure function, given the same input it always outputs the same value, it doesn’t modify any state outside the declared function (in fact in this function no modification is done at all, it just returns the square).

Pure functions are deterministic, simple and are very easy to write unit tests. In a mathematical sense they are just a mapping from input to output, it’s somewhat different from the old concept of a function being a procedure of wrapped code which can be reused.

The next point is the immutability, immutable data structures are data structures which are not modified after their declaration. Instead of mutating data structures or primitive values you try to create new states based on the values of the already declared values.

For instance in JavaScript const was created to make primitive values immutable so their value cannot be mutated after declaration.

It’s worth mentioning that while practicing immutability leads to deterministic programs, and can potentially fix many concurrency issues in a real-world practical application it’s not possible to conform 100% to it, at some point of our programs we have to deal with side-effects, like I/O, API calls etc, etc. Even the stricter/pure functional programming languages like Haskell have a way to deal with side-effects with the concept of Monads.

And finally, In JavaScript functions are first-class citizens meaning they can be:

  • assigned to a variable,
  • functions can be passed as parameters and
  • returned by another function.

For example:

const squareNumber = function(num) {
return num * num
}

//arrow function, we can assign a function to a variable
const squareNumber = num => num * num;
const incrementNumber = num => num + 1;

// a function can be composed of other functions: function composition
const squareAndIncrement = num => squareNumber(incrementNumber(num));
console.log(squareAndIncrement(4)); // Outputs: 25

// Here is an example where we pass a function as
// an argument to another function
const applyFunction = (func, num) => func(num);
console.log(applyFunction(incrementNumber, 4)); // Outputs: 5
console.log(applyFunction(squareNumber, 5)); // Outputs: 25

// a function can return another function
function curryAdd(x) {
return function(y) {
return x + y;
};
}

const add5 = curryAdd(5); // This is a function that adds 5 to its argument
console.log(add5(3)); // prints 8

This leads to a very powerful paradigm where functions can be used as a unit of composition.

Currying, closures, partial applications and point free function composition

Function as fundamental unit of composition was inspired by Lambda Calculus, an universal model of computation where functions are atomic building blocks.

In Lambda Calculus, functions are always anonymous, functions are unary (meaning they only accept only one parameter), and functions are first-class citizens. For example:

const incrementNumber = x => x + 1;

Now, the transformation of n-ary function (function with multiple parameters) to a series of unary parameters is called currying. Let’s take an example with fetchData.

function fetchData (baseUrl, endpoint) {
return baseURL + endpoint;
};

What if we transform this function with two parameters into a two-series of functions with each function having one parameter. What benefits do we have from that?

We have the benefit of creating a specialised version of fetchData function, with fetchDataFromApi and fetchDataFromAnotherApi each containing different versions of baseUrl, then calling different endpoints as well.

// A function to fetch data from a URL:
function fetchData(baseUrl) {
return function(endpoint) {
const url = baseUrl + endpoint;
// Fetch data from the URL...
}
}

// Curry `fetchData` to create a new function that
// fetches data from a specific base URL:
const fetchDataFromApi =
fetchData('https://api.example.com/');

const fetchDataFromAnotherApi =
fetchData('https://anotherApi.example.com/');

// Use the new function to fetch data from various endpoints:

console.log(fetchDataFromApi('/users'));
// 'https://api.example.com/users'

console.log(fetchDataFromAnotherApi('/articles'));
// 'https://anotherApi.example.com/articles'

So, now. From the example above we come across two important features in JavaScript: closures and partial applications.

In JavaScript, a closure is essentially the inner function’s ability to remember and access variables from the outer (parent) function’s scope, even after the outer function has finished executing. This provides flexibility in creating functions with ‘remembering’ capabilities, enabling data privacy and other powerful programming patterns.

Like in the following example, the variable count defined in the outer function scope of createCounter is available to functions like increment, decrement, getCount, this access to the count variable is the concept of the closure in JavaScript. It’s also a feature which allows us to write private variables in JavaScript.

function createCounter() {
let count = 0; // private variable

return {
increment: function() {
count++;
},
decrement: function() {
count--;
},
getCount: function() {
return count;
}
};
}

const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // Output: 2

Both currying (process of transforming n-ary functions to unary functions) and closures allow us to have the partial application feature in JavaScript which can be a beneficial feature to use when composing software. Like, the following simple example:

const increment = x => y => x + y;

const incrementBy1 = increment(1);
const incrementBy20 = increment(20);

console.log(incrementBy1(10)) // Logs: 11
console.log(incrementBy20(10)) // Logs: 30

So, from the general increment function we created two special functions incrementBy1 and incrementBy20 doing respectively what the variables were named for.

To return to function composition, just as we can combine multiple functions to create more complex operations we have the option of a more elegant composition called point-free style.

Point-free style in functional programming refers to a coding approach where function definitions do not explicitly mention their arguments, also known as “points”. Instead, functions are used and composed using function composition and other higher-order functions. The aim of this style is to reduce and simplify function definitions for more readable and maintainable code.

const pipe = (...fns) => x => fns.reduce((y, fn) => fn(y), x);

//If you want to implement traditionally the pipe function,
// say with for loop, here is an example.
function pipeImp(...fns) {
return function(x) {
let acc = x;
for (let i = 0; i < fns.length; i++) {
acc = fns[i](acc);
}
return acc;
}
}
const trace = label => value => {
console.log(`${ label }: ${ value }`);
return value;
};
const increment = n => n + 1;
const multiply = n => n * 2;

const example1 = pipe(
increment,
trace("after increment"),
multiply,
trace("after multiply")
);

const divideBy2 = n => n / 2;

const example2 = pipe(
increment,
trace("after increment"),
multiply,
trace("after multiply"),
divideBy2,
trace("after divideBy2")
);

Built-in higher order functions in JavaScript and the concept of functors.

Higher-order functions are a key concept in functional programming. These are functions that can take other functions as arguments and/or return functions as results. They allow us to create abstractions over actions, not just values.

JavaScript array methods like map, filter, find and reduce are prime examples of higher-order functions where they take a function as argument and apply this function to each element in an array.

[1, 2, 3, 4].map(x => x + 2).map(x => x * 2); // Output: [6, 8, 10, 12]

Array in this case is a data type which can be mapped over, and usually the data types which can be mapped over are called functor data types.

Although in JavaScript and in other languages the .map is available for arrays this doesn’t mean that we cannot map over string, number, objects. They can be made mappable/functors as well.

const Functor = value ({
map: fn => Functor(fn(value)),
fold: () => value
});

const randomFunctor = Functor(2).map(x => x + 2).map(x => x + 10).fold();
// randomFunctor: 14; On first map we increment by 2 which makes it 4, then
// chaining up with another map which increments by 10 making the value 14,
// then in the end we just fold/extract the value.


// Or with an object, Base warrior object
const warrior = {
name: 'Unit 1'
};

// Functions to modify the object
const withType = type => obj => ({ ...obj, type });
const withWeapon = weapon => obj => ({ ...obj, weapon });
const withLevel = level => obj => ({ ...obj, level });
const levelUp = obj => ({ ...obj, level: obj.level + 1 });

// Now let's use the Functor to build our character
const newWarrior = Functor(warrior)
.map(withType('Swordsman'))
.map(withWeapon('Excalibur'))
.map(withLevel(1))
.map(levelUp)
.fold();

console.log(newWarrior);

// Output: { name: 'Unit 1',
// type: 'Swordsman',
// weapon: 'Excalibur', level: 2 }

//Essentially we implemented the builder pattern in this particular case
// via Functor, the difference being functions are not part of any object
// they can be reused anywhere else as self-contained units.

From the above, we see that although traditionally we are accustomed to pass values to functions, we can also reverse that, pass functions to values instead, it looks like we were already doing it all the time when we were using map higher order function at arrays, and the values that can be mapped over are called Functor.

Using functions to generate objects a.k.a Factory Functions.

As the saying goes:

Sometimes, the elegant implementation is just a function. Not a method. Not a class. Not a framework. Just a function.

Throughout our careers we always thought of classes/objects as the primary unit of composition when building Software. Functions in this mindset usually are part of classes/objects and they usually act as agents of abstracting the way the object state is modified, it simplifies the internal mechanism of the object. Well, that’s a great approach.

But what if we thought of functions as the primary unit of composition?

That’s what React did, originally React components were classes, then with the newer upgrades the unit of composition was changed to functions:

// Class Component
class HelloWorld extends React.Component {
render() {
return (
<div>
Hello, world!
</div>
);
}
}

// Function Component
function HelloWorld() {
return (
<div>
Hello, world!
</div>
);
}
// JSX essentially is transformed in a React.createElement call
function HelloWorld() {
return React.createElement('div', null, 'Hello, world!');
}

//How React hypothetically transforms the createElement into a literal object
function HelloWorld() {
return {
"type": "div",
"props": {
"children": "Hello, world!"
},
"key": null,
"ref": null,
"_owner": null,
"_store": {}
};
}

Technically, Components in React are factory functions that return an object and that are responsible for creating and returning instances of UI elements. These components define the structure, behaviour, and appearance of the elements they represent.

Moreover, private variables are possible via the concept of closures.

Similar approach was taken by Vue.js as well, though the main unit of composition is still object literal, somehow they managed to make factory functions as more central to their composition with Composition API.

<!-- HelloWorld.vue Vue.js 2 -->
<template>
<div class="hello">
<h1>{{ message }}</h1>
</div>
</template>

<script>
export default {
data() {
return {
message: 'Hello, world!'
}
}
}
</script>
<style scoped>
.hello {
color: red;
}
</style>

Vue.js 3

<template>
<div>
{{ message }}
</div>
</template>

<script>
import { ref } from 'vue';
export default {
setup() {
const message = ref('Hello, World!');
return {
message
};
}
}
</script>

With setup() being the factory function.

To continue, let’s take some more examples on factory functions. Like for example implementing the queue data structure.

const buildQueue = () => {
let queue = [];

const checkIfEmpty = () => queue.length === 0;

return {
insert: (x) => {
queue.unshift(x);
},
remove: () => {
return queue.pop();
},
top: () => {
if (checkEmpty()) return undefined;
return queue[0];
},
get size() {
return queue.length;
},
checkIfEmpty,
};
};

const queue2 = buildQueue();

or implementing prototypal delegation

const animalPrototype = {
eat() {
console.log('Eating...');
},
};

function createAnimal(name) {
const animal = Object.create(animalPrototype);
animal.name = name;

return animal;
}

const cat = createAnimal('Cat');
cat.eat(); // Output: Eating...
console.log(cat.name); // Output: Cat

or with using pipe as composition

const pipe = (...fns) => x => fns.reduce((acc, fn) => fn(acc), x);

const canFly = obj => ({
...obj,
canFly: true,
fly(vx, vy, vz) {
// Do something with the velocities
},
});

const canSwim = obj => ({
...obj,
canSwim: true,
swim(vx, vy, vz) {
// Do something with the velocities
},
});

const canWalk = obj => ({
...obj,
canWalk: true,
walk(speed) {
// Do something with the walking speed
},
});

const createHawk = pipe(canFly);
const createPenguin = pipe(canSwim, canWalk);
const createDuck = pipe(canFly, canSwim, canWalk);

const hawk = createHawk({});
const penguin = createPenguin({});
const duck = createDuck({});

console.log(hawk);
console.log(penguin);
console.log(duck);

or, we can use the new object spread operator in JS. Composition in JS with this is quite straightforward and comes handy on many occasions.

const canCodeFrontEnd = () => ({
canCodeFrontEnd: true,
codeFrontEnd(task) {
// Write front-end code for a given task
},
});

const canCodeBackEnd = () => ({
canCodeBackEnd: true,
codeBackEnd(task) {
// Write back-end code for a given task
},
});

const canTest = () => ({
canTest: true,
test(testCase) {
// Test a specific case
},
});

const FrontEndDeveloper = () => ({
...canCodeFrontEnd(),
});

const BackEndDeveloper = () => ({
...canCodeBackEnd(),
});

const Tester = () => ({
...canTest(),
});

const FullStackDeveloper = () => ({
...canCodeFrontEnd(),
...canCodeBackEnd(),
...canTest(),
});

Now, factory functions can be quite handy when it comes to composing software. Personally, I find them appealing and often prefer using them. However, I don’t want to make a bold statement suggesting that factory functions should be used over classes or constructor functions in JavaScript. It ultimately comes down to personal preference and what you feel most comfortable with. Classes in JavaScript are built-in constructs that mimic traditional class-based programming and there is nothing inherently wrong with using them. That being said, there are alternative ways to build and compose software, such as leveraging functions as the primary unit of composition. Factory functions offer a smaller and more focused approach, without the complexities of constructors and the concept of “this”. Additionally, they make great use of closures, allowing for encapsulation and privacy of data. Ultimately, the choice between factory functions and classes depends on the specific requirements and style preferences of the project or developer.

Summary

  • JavaScript has an interesting take on OOP. It cuts class abstraction away and uses objects directly. Objects can be linked between each other via prototypal inheritance/delegation, so a new object prototypically linked with an existing object does not copy its property, instead it can delegate to, and dynamically execute within the context of execution (dynamical this). Objects in JS can be declared directly via object literal ({}), via constructor functions and the recently added class keyword.
  • Functions in JavaScript are first-class citizens, meaning we can assign them to variables, pass them as parameters and return a function from another function. JavaScript provides many functional programming capabilities but it doesn’t force you to use them (like pure functional programming languages do like Haskell do). The concepts of closures, currying, pure functions, immutability, partial applications and higher-order functions are extremely handy in composing software in our daily life. It makes our programs more declarative. We didn’t dwell on all topics of functional programming , especially digging deep into monads and transducers since we focused on core principles.
  • Functions can be used as a unit of composition as well, to create/generate objects. And this is where functional programming meshes up with OOP, giving rise to paradigms like factory functions that marry JavaScript’s functional abilities with object creation and manipulation. This has been embraced in popular frameworks like React, where components — essentially factory functions — generate JSX elements, encapsulating the user interface’s state and behaviour.

--

--