Top Rated Plus on Upwork with a 100% Job Success ScoreView on Upwork
retzdev logo
logo
tech

Functional vs. Imperative Programming

by Jarrett Retz

November 6th, 2023 programming javascript typescript functional programming

In the last year, I've almost completely switched from writing a mix of mostly imperative Javascript to mostly functional Javascript. The change occurred when I was hired to work on a project with developers who hailed from the Haskell programming world.

I was shocked to see pipe and flow functions combined with "lenses", which I couldn't comprehend. The team had written React components, but it was hard for me to make sense of them.

I spent a couple of months working through small books and articles like:

I discovered benefits and joys when writing functional code in Javascript. Similarly, I started using libraries that helped me write functional code (with monads). In this article, I won't explain:

  • why functional programs work well in Javascript (functions as objects, closures, higher-order functions)
  • core functional programming concepts
  • working with types and Typescript

However, I will show you two different ways to solve a problem: one imperative, the other functional. Finally, I'll explain why I favor the functional approach.

What is Imperative Programming?

Imperative programming uses statements or commands. An example of imperative programming in Javascript is writing a for loop. Similarly, another example is using an if/else statement.

// for loop
for (let i = 0; i < array.length; i++) {
  // modify state
}

// if else
if (condition) {
  // modify state
} else {
  // do something else
}

You can read more about imperative programming on Wikipedia.

What is Functional Programming?

Functional programming is a large topic. For the scope of this article, we'll simply provide an example in Javascript that juxtaposes imperative programming.

As mentioned in the previous section, an example of an imperative control flow statement is a for loop in Javascript. It's often used to iterate over an array. Another way to iterate over an array is the Array.prototype.map() function.

More commonly, it's used like this:

const array = [1, 2 3];

const func = (num) => num + 1;

// map
array.map(func)

The map method accepts a function to call on each element and handles the iteration works.

Comparing Imperative and Functional Programming in Javascript

To demonstrate the two different approaches let's look at a simple problem and provide two different solutions.

Consider we have two arrays of data. The first is a list of objects with an id and name. The second array is a list of objects with an id, name, and color.

const data1 = [
	{
		id: 1,
        name: 'Mug',
	},
	{
		id: 2,
        name: 'Table'
	},
	{
		id: 3,
        name: 'White board'
	},
	{
		id: 4,
        name: 'Book'
	},
    {
        id: 5,
        name: 'Coin'
    },
    {
        id: 6,
        name: 'Laptop'
    }
];

const data2 = [
	{
		id: 45,
        name: 'mug',
        color: 'black'
	},
	{
		id: 46,
        name: 'table',
        color: 'black'
	},
	{
		id: 47,
        name: 'White board',
        color: 'white'
	},
	{
		id: 48,
        name: 'Book',
        color: 'red'
	},
	{
		id: 48,
        name: 'COaster',
        color: 'green'
	},
];

The goal is to create a new list of objects that have the id from the first list of objects, a capitalized name value, and a corresponding color value.

To achieve this, we'll match the name from data1 to the name of data2. Then, we'll take the color value and add it to the object in data1.

Imperatively, this could look like:

/**
 *
 *
 * Imperative
 *
 *
 */
const combine = (
	items: typeof data1,
	itemsColorMap: typeof data2
): typeof result => {
	let itemsWithColor: { id: number; name: string; color: string }[] = [];

	for (let i = 0; i < items.length; i++) {
		const temp = items[i];

		const foundMatch = itemsColorMap.find(
			(value) => value.name.toLowerCase() === temp.name.toLowerCase()
		);

		if (foundMatch) {
			itemsWithColor = [
				...itemsWithColor,
				{
					...items[i],
					color: foundMatch.color,
				},
			];
		}
	}

	return itemsWithColor;
};

In truth, it's not 100% imperative, but it uses the `for` loop.

Functionally, and with the help of the fp-ts library. This could look like:

/**
 *
 *
 * Functional
 *
 *
 */
const combine_fp = (items: typeof data1, itemsColorMap: typeof data2) =>
	pipe(items, A.map(tryToMatchItemColor(itemsColorMap)), A.compact);

Likewise, this is not 100% functional, but close. Also, you might be wondering where the rest of the code is! Well, it's here in all these functions:

/**
 *
 *
 * Functional
 *
 *
 */
const itemEq: <T extends { name: string }>() => Eq<T> = () =>
	fromEquals((x, y) => x.name.toLowerCase() === y.name.toLowerCase());

const compareItemNames: <T extends { name: string }>(
	x: T
) => (y: T) => boolean =
	<T extends { name: string }>(x: T) =>
	(y: T) =>
		itemEq().equals(x, y);

// Using a lens (i.e., monocle-ts) would be more appropriate here
const accessColor: <T extends { color: string }>(a: T) => string = (a) =>
	a.color;

// Using a lens (i.e., monocle-ts) would be more appropriate here
const includeColor: <T extends { id: number; name: string }>(
	a: T
) => (color: string) => { id: number; name: string; color: string } =
	(a) => (color) => ({ 
		id: a.id, name: a.name, color });

const tryToMatchItemColor: (
	itemsColorMap: typeof data2
) => (
	item: (typeof data1)[0]
) => O.Option<{ id: number; name: string; color: string }> =
	(itemsColorMap: typeof data2) => (a: (typeof data1)[0]) =>
		pipe(
			itemsColorMap,
			A.findFirst(compareItemNames(a)),
			O.map(accessColor),
			O.map(includeColor(a))
		);

const combine_fp = (items: typeof data1, itemsColorMap: typeof data2) =>
	pipe(items, A.map(tryToMatchItemColor(itemsColorMap)), A.compact);

You might think that it's unfair to hide the smaller functions created that were used as building blocks for the final function call, but I'm using it as a way to demonstrate the benefits of functional programming

At the risk of sounding preachy, functional approaches take pure functions and compose them to make more complex programs. The programmer has smaller functions, which may be easier to test, and—in theory—make it more difficult to write a bug than imperative statements.

View gist here

There's a rich discussion online about the benefits of functional programming and how to compare it to OOP or against imperative styles. A quick Google search will bring up more than enough content to keep you busy.

Conclusion

In truth, I often mix the approaches. I still use if/else statements, but for arrays, I'm always leveraging the fp-ts library. I started using the fp-ts library in conjunction with the monocle-ts library and it seems impossible now for me to write Typescript any other way!

Jarrett Retz

Jarrett Retz is a freelance web application developer and blogger based out of Spokane, WA.

jarrett@retz.dev

Subscribe to get instant updates

Contact

jarrett@retz.dev

Legal

Any code contained in the articles on this site is released under the MIT license. Copyright 2024. Jarrett Retz Tech Services L.L.C. All Rights Reserved.