Tech insights: What I Learned About Functional Programming in JavaScript

What I Learned About Functional Programming in JavaScript

By Alpha Shuro

At first glance, functional programming seems unnecessarily difficult to learn and generally redundant. That said, it has gained a lot of traction in recent years for the benefits it offers to write better and cleaner code. In this article, I’ll explain - in jargon-free English - what problems it solves for me, how it solves them, and how I use it to address real-life issues.

Alpha_Laptop-being-cleaned_What-I-Learned-About-Functional-Programming-in-JavaScript_Inner-Article-Image-02-1

What is functional programming?

Functional programming is formally a programming paradigm: A set of rules that define how to structure your program in a way that will achieve a certain goal, in a functional way as opposed to a procedural way.

Some consider it simply for its aesthetics, as a stylistic addition to their solution, whilst others see it as a mindset that you apply right from the beginning of the problem solving process.

Here is a simple example, illustrating a procedural and a functional language:

Alpha_Laptop-being-cleaned_What-I-Learned-About-Functional-Programming-in-JavaScript_Inner-Article-Image-2--2

How and why I started using it

After learning procedural and object-oriented programming in university, I got the impression that functional programming was designed only for scientists and mathematicians. I did, however, become curious when I saw it gain popularity over the recent years, and I wanted to understand what problem it solved.

I experimented with it, and at first didn’t find it worth the steep learning curve it required. After a rollercoaster of emotions, immersion and discovery, I realised what functional programming offered: confidence, communication, and legibility.

At the time, I was at a company building a product that automates engagement on social media. Like any other software project in existence, it grew more complex over time and I wondered how to make it more intuitive for new (and existing) developers, while also reducing the surface area for bugs.

After experimenting with functional programming, I realised that it had the potential to solve that problem by presenting information in a way the human brain can absorb easily, while enforcing rules that prevent common logic errors. It proved incredibly beneficial for my context because it:

  • allowed team members to understand the code easily,
  • made us more confident in the code we wrote, and
  • made it easier to follow the logic, without getting overwhelmed by noise.

In the following sections, I’ll explore the language features that are generally required in order for a language to be considered ‘Functional’, the restrictions or laws that functional programming enforces on such languages, and how to think about the two in relation to the problem being solved.

How functional programming is implemented

Language features

The first requirement of a functional programming language is first class functions. Although JavaScript has always supported this requirement, there were still a lot of tools missing from the functional tool belt, and these were commonly added through the use of libraries like lodash.

However, when ES6 was released, JavaScript began to add a more comprehensive functional programming tool belt that allowed developers to focus on the actual operations they were performing, rather than defining operations using constructs like for.

Restrictions

Functional programming implementations and libraries are usually coupled with a few restrictive rules, for example pure functions and immutable values.

Creating complex programs with these seems impossible at first, which creates the artificial learning curve that most people experience. But sticking to these restrictions (especially during deep-dives in pure functional languages) opened my eyes to the true value of functional programming.

A model of thought: Lists, pure functions, composition of operations

Once I had embraced the two ideas above, my mental model for solutions began to evolve from a model based on giving instructions on how to manipulate state, to a model based on visualising the flow of data as lists, and calculating each step as a whole that stands alone.

How I learned functional programming

Understand the language

Much of functional programming is based on concepts that are already part of the English language and are therefore easily understood using existing knowledge.

As a native Shona speaker having learnt English as a second language, I found that it was important (even for native English speakers) to understand the basic English meaning of the terms used in order to have a more intuitive understanding of the concepts.

Learn the dictionary English definition of all the terms you come across in order to build solid abstractions.

Learn the patterns

There are a few techniques commonly used to solve problems in a functional manner. For example, recursion is commonly used to solve many problems for which a procedural programmer would use a combination of for and array.push.

Practise functional programming exercises from places like Hackerrank in a strict functional language (e.g. Haskell, Closure, F#) to learn these patterns, as they form part of the recipe for many larger solutions.

Index the operations

All functional programming libraries in JavaScript have a large library of operations available by default, for use in composing your application. But, upon closer inspection, I found that most of these operations are simply repeated/replicated by each different library, and every now and then they may have a different name.

Armed with this information, I selected one library (Ramda) and read through all of the available functions. Rather than memorising them, I created a mental index of what was available to me. This allowed me to quickly recognise when there was already a solution for an operation I was trying to perform, and know what to Google for.

What it solved for me, and how

Confidence

Repetitively writing constructs like for loops and manually managing mutation takes up a lot of mental real-estate in a programmer’s mind, and this introduces a large surface area for making mistakes.

Functional programming gives me confidence in my code by removing the need for these and giving me a toolbelt of functions I can use to build my program. I only need to define the unique parts of my program, like properties and business logic, then compose them together using this tool belt.

Communication

The sheer complexity involved in manually keeping track of variables being mutated and other procedural constructs make it cognitively expensive for a reader to understand exactly what the code is doing.

The philosophy of building small, pure functions allows me to communicate efficiently by making my intentions clear to the next developer, be it someone else or myself in three months. If the function abstracts only one operation, is aptly named, and fits into an operation that is already well defined - for example, a filter - the next developer already knows that there’s a list of things, and that list is being reduced to a smaller list using a yes or no condition, and only needs to understand the name of the function being passed in to the filter to tell what that condition is.

Readability

A little known fact is that when a developer is writing code, their main audience other developers. Machines can understand code regardless of how readable or unreadable, but humans need information to be chunked into consumable pieces that they can recognise and abstract away quickly.

Readability is most impacted by familiarity, and functional programming achieves this by using the aforementioned tool belt of functions. They are similarly named in almost every functional language, and use a naming convention that is (mostly) designed to be easily recognisable.

This also removes syntactical noise, allowing the code to be read like plain English.

Example

As a small introduction, we’re going to go through a piece of procedural JavaScript, identify some problems that are obscured by the procedural nature of the code, and implicitly solve these problems by simply changing it to functional.

Procedural version

See if you can find what’s wrong with the code below:

var numbers = [4,10,0,27,42,17,15,-6,58];
var faves = [];
var magicNumber = 0;

for (let i = 0; i < numbers.length; i++) {
    if (numbers[i] >= 10 && numbers[i] <= 20) {
        faves.push( num );
    }
}

for (let i = 0; i <= faves.length; i++) {
    magicNumber += faves[i];
}

var msg = `The magic number is: ${magicNumber}`;
console.log( msg );

Notice that, besides the fact that it’s difficult to immediately tell what this code’s purpose is, there are also two bugs present in the code: num is not defined, and i <= faves.length

We could opt to fix these bugs, and add comments explaining the mess, but functional programming suggests that a programmer shouldn’t have to think about these minor details. Instead, they should only need to focus on the problem they are solving. The first step to converting it to functional (thereby fixing the identified issues) is to apply the first law of functional programming: everything is a function.

First step to transitioning: ‘Functions’

Let’s put this code in a function:

var numbers = [4,10,0,27,42,17,15,-6,58];

var msg = `The magic number is: ${magic(numbers)}`;
console.log( msg );

function magic(list) {
    var faves = [];
    var magicNumber = 0;

    for (let i = 0; i < list.length; i++) {
        if (list[i] >= 10 && list[i] <= 20) {
            faves.push( num );
        }
    }

    for (let i = 0; i <= faves.length; i++) {
        magicNumber += faves[i];
    }

    return magicNumber;
}

Notice how the requirement has now imposed a name on the code, magic, that implicitly serves as a descriptor for the purpose of the function.

In the next step, let’s abstract different parts of the code into chunks for easier consumption by the brain:

var numbers = [4,10,0,27,42,17,15,-6,58];

var msg = `The magic number is: ${magic(numbers)}`;
console.log( msg );

function magic(list) {
    var favorites = pickFavorites(list);
    var magicNumber = sum(favorites);

    return magicNumber;
}

function pickFavorites(list) {
    var favorites = [];
    for (let i = 0; i < list.length; i++) {
        if (isFavorite(number)) {
            favorites.push( num );
        }
    }
    return favorites;
}

function isFavorite(number) {
    return number >= 10 && number <= 20;
}

function sum(list) {
    var total = 0;
    for (let i = 0; i <= list.length; i++) {
        magicNumber += faves[i];
    }
    return total;
}

We’ve separated the code into semantic chunks, so it should now be a lot easier for a reader to understand what the magic function is doing simply by reading the names of the functions used inside it.

You will, however, notice that our bugs still exist in this piece of code. To fix them, we’re going to get rid of the procedural loop and mutation combo, and sprinkle some functional purity:

var numbers = [4,10,0,27,42,17,15,-6,58];

var msg = `The magic number is: ${magic(numbers)}`;
console.log( msg );

function magic(list) {
    var favorites = pickFavorites(list);
    var magicNumber = sum(favorites);

    return magicNumber;
}

function pickFavorites(list) {
    return list.filter(isFavorite);
}

function isFavorite(number) {
    return number >= 10 && number <= 20;
}

function sum(list) {
    return list.reduce(add);
}

function add(x, y) {
    return x + y;
}

We’ve eliminated the bugs by using functional abstractions over common operations, like reduce and filter. Functions like this are part of the fucntional programming toolbelt, common across all languages that claim to be functional, with some of them being available via libraries.

Although we’ve solved all of the issues in the code, and it has reached a point of relatively good legibility and ease of consumption, there is one extra step we can take to truly make it functional and reach the peak of conciseness: composition.

Composition defines our function as a holistic view of chaining a group of smaller functions. I found that this removes variable noise (i.e. list) and allows the reader to focus purely on the core operations used in the solution.

Now let’s rewrite the example using function composition:

var numbers = [4,10,0,27,42,17,15,-6,58];

var msg = `The magic number is: ${magic(numbers)}`;
console.log( msg );

// const magic = list => sum(pickFavorites(list)) // <- composition without extra tooling
const magic = pipe(pickFavorites, sum); // <- composition with a helper function

const pickFavorites = filter(isFavorite);

const sum = reduce(add);

function isFavorite(number) {
    return number >= 10 && number <= 20;
}

function add(x, y) {
    return x + y;
}

This example, however, relies on the fact that all the functions used are simply using the parameters to calculate a return value. In the real world, we need to execute side effects like triggering asynchronous operations or printing values to the console.

Asynchronous operations are generally represented using monads, which is a topic that would require its own blog post. However, they can be achieved functionally in JavaScript using libraries such as RxJS. Logging can be illustrated with our previous example:

var numbers = [4,10,0,27,42,17,15,-6,58];

var msg = `The magic number is: ${magic(numbers)}`;
console.log( msg );

const magic = pipe(
  pickFavorites, 
  tap(console.log), 
  sum
);

const pickFavorites = filter(isFavorite);

const sum = reduce(add);

function isFavorite(number) {
    return number >= 10 && number <= 20;
}

function add(x, y) {
    return x + y;
}

The function used above, tap, is a function that runs the given function with the parameters it receives, then returns the original argument passed into it, thus allowing you to execute side effects without affecting the chain.

Useful tools for learning and using functional programming

  • Ramda, Sanctuary, Lodash, Underscore - these libraries add various functions to your functional programming toolbelt, and enable you to apply functional programming fully in JavaScript. Choose one, read through the available functions, then try to use it to compose your applications.
  • React, Elm - these libraries allow you to implement a functional programming architecture in building front-end user interfaces for the web.
  • Functional Light JavaScript - this book is absolutely essential as an introduction to functional programming in JavaScript.

Alpha Shuro is a Software Engineer at Blue Robot. He enjoys building high quality tech and communicating intent through code. He philosophises and experiments with AI in his free time, and is a firm believer in the potential of socio-technological advancement in Africa. If you want to follow him online, you can do so on Twitter and Medium!

Source-banner--1-

Cat eyes@2x

Subscribe to our blog

Don’t miss out on cool content. Every week we add new content to our blog, subscribe now.