Fun JavaScript: Safely dealing with undefined/null values using functional programming

Fun JavaScript: Safely dealing with undefined/null values using functional programming

Originally posted on intext.

Let’s pretend there is a number we want to apply an operations to, and if that number is undefined/null, we want to avoid an error or unexpected result. The operation will be add.

One way to do this imperatively could be:

const number = // getting number from somewhere (DB/network/etc)let newNumber;
if (number) {
    newNumber = add(number, 5);
}// Another 50 lines below you might find something like this:if (number) {
    newNumber = add(newNumber, 3);
}

What about a function that prints the number to the console or a message if this one is missing?

const revealNumber = (number) =>
    console.log(number ?? 'There was no number to reveal');revealNumber(newNumber);

We all know that real production code is not as simple as these examples, so in reality, our code base gets cluttered with a lot of boilerplate checks for the presence of a value before attempting to do some operation using it. I am a functional programming fan, although I never tell anyone that it is better than other paradigms (like OOP). Instead, I encourage developers to learn this paradigm and then find ways to combine it with OOP, taking the best of both words, and creating a powerful combination.

JavaScript is, in my humble opinion, one of the best languages to do this, you can do OOP and you can also do functional programming, and none of these styles feels awkward to the language, like functional programming does sometimes when I use certain functional techniques in C#, or Java. Let’s see how functional developers approach the “possibly missing but possibly there” problem, the Maybe Monad. If you are a Java developer, you are probably already familiar with Optional<T>, if you are a C# developer, you can read my article https://itnext.io/fun-csharp-dealing-with-null-values-in-a-safe-and-elegant-way-4ffdf86a38c8, where I talk about my LeanSharp library (https://github.com/ericrey85/LeanSharp/).

The Maybe Monad is a functional programming concept, which you can read a lot about on the internet, and that follows three Mathematical laws, left-identity, right-identity and associativity. I am not here to talk about that, there is plenty you can find online if you want to dive deeper into the Mathematical side of it. For the purpose of this article, think about the Maybe Monad as a container for a value, which will allow you to perform safe operations on that value. The basics of how it works are: “If the value is there, I will perform your operation, but if it not, nothing is going to happen (I will not let you down with an error)”. A very simplistic definition could be as follows:

const isNil = (o) => o == undefined;
const notNil = (o) => !isNil(o);const Maybe = (value) => ({
    map: (fn) => notNil(value) ? Maybe(fn(value)) : Maybe(),
    bind: (fn) => notNil(value) ? fn(value) : Maybe(),
    getOrElse: (defaultValue) => value ?? defaultValue
});

What do we have here? Let’s see…we have a factory function (Maybe), that takes a value as a parameter, and returns an object with three methods:

1- map: It takes a function, and if the value exists, applies that function on the value and returns a new value inside the same Maybe container (although a new instance). If the value is not there, it will just return a Maybe wrapping undefined.

2- bind: Similar to map, but the function that is applied returns another Maybe, instead of a simple value.

3- getOrElse: After all is said and done, it allows us to access the contained value or return the supplied default value if what we have at that point is a Maybe(undefined).

Let’s see now how to use this Maybe Monad.

const result = Maybe(number)
    .map((x) => x + 3)
    .bind((x) => Maybe(x + 5))
    .getOrElse('There was no number to reveal');console.log(result);

If above code seems a bit weird to you, which I would expect if you have never done functional programming, let’s do something similar using an array and its functions.

const result = [number]
    .filter(notNil)
    .map((x) => x + 3)
    .flatMap((x) => [x + 5])[0] ?? 'There was no number to reveal';

We have “lifted” the number into a container, in this case, an array, we have excluded the value if it is undefined/null (by using filter), and that has allowed us to perform safe operations on it. Our Maybe’s bind function is like the array’s flatMap (this function is also known as chain). If you have not yet noticed, [0] ?? ‘There was no number to reveal’ in the last line is the equivalent of Maybe’s getOrElse function. Obviously this last example is not exactly the same, but I wanted to show you something similar, very similar, so you could have a comparison point with something that you are probably already familiar with.

Before ending the article, let’s see a more declarative/functional approach. If you are familiar with Reactive Programming (RxJS), you already know the pipe function. If you are not, I will show it to you now, it is a function that takes an array of functions and returns another function. This returned function takes an initial value and then uses it as the starting point to apply all the previously supplied functions. That probably did not mean much to you, so let’s see it in action.

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

Think about it like this, if you have two functions, f, and g, and an initial value x; imperatively, you could do this:

const finalResult = f(g(x));

You could also break it down and do this instead:

const firstResult = g(x);
const finalResult = f(firstResult);

By using pipe, you could accomplish the same like this:

const getFinalResult = pipe(g, f);
const finalResult = getFinalResult(x);

I hope it makes more sense now, pipe, effectively, allows for function composition. Another technique used in functional programming is called currying, which could be thought of as a function that takes another function with n parameters, and converts it into n functions that each take one single parameter and return another function that follows the same pattern until the last function that returns the result. Let’s see a curried function:

const add = (a) => (b) => a + b;
const increment = add(1);
const incrementedValue = increment(10); // 11

We are not going to use a function to curry our functions here, instead, we will curry them manually, you can find such function in libraries like ramda. Bear in mind that in production code, you will want to use a function to curry your functions for you (instead of doing it manually), although you should know that the curry function and default parameters do not play nicely if combined together. If you used such function, it would look like this:

const add = curry((a, b) => a + b);

Currying is a technique that allows for function reuse (like seen above with increment), and function composition using something like the pipe function (or its sibling compose). Now, let’s provide curried implementations for mapbind and getOrElse that can be decoupled from a Maybe object:

const map = (fn) => (o) => o.map(fn);
const bind = (fn) => (o) => o.bind(fn);
const getOrElse = (defaultValue) => 
    (o) => o.getOrElse(defaultValue);

In each case, we have used duck typing and delegated the call to the passed in object. We can now rewrite our code like this:

const getFinalResult = pipe(
    Maybe,
    map(add(3)),
    bind((x) => Maybe(x + 5)),
    getOrElse('There was no number to reveal')
);console.log(getFinalResult(number));

Noticed that the add function used above is the curried version. The pipe function reads top to bottom (or left to right), if you feel more comfortable reading bottom to top (or right to left), then the function you want to use is compose, it does the same that pipe, but starting with the bottom/right most function. You could implement compose like this (basically using reduceRight instead of reduce):

const compose = (...fn) => (n) => fn.reduceRight(
    (acc, fn) => fn(acc), n
);const getFinalResult = compose(
    getOrElse('There was no number to reveal'),
    bind((x) => Maybe(x + 5)),
    map(add(3)),
    Maybe
);

There are several JavaScript libraries that provide you with the functional constructs you need to write functional programs, some of the most famous ones are ramda, underscorejs, lodash and folktale. I encourage you to take a look at them, and use them (instead of using your own implementations) whenever feasible and possible.

I have always heard some developers saying things like “this way of programming is confusing”, or “you would not write an entire system using approaches like this one”. I have come to understand those type of feelings, they come from not being familiar with it, and from bias toward what we already know. I have personally written large programs using techniques like the ones shown in this article, and using other monads, like the Either Monad, the IO Monad, etc. And what I have come to realize is that those programs had a lower bug density than the ones I wrote with a purely imperative approach. I encourage you to step out of your comfort zone, and to learn something you do not know or understand yet, I promise you, it will only make you a better developer.

Written by Eric Rey

Source: intext