Functional programming – The ultimate beginner’s guide

This article has two goals:

  • To show you why functional programming is useful.
  • To show you the basics of functional programming.

Introduction

Before jumping into the techniques of functional programming, let’s examine the motivation for it, some prerequisites and some other things.

This article focuses on the motivation for functional programming

One of the frustrations I’ve had with some functional programming resources is that the motivation they give for using functional programming is weak.

In some resources, there is no explanation at all for why functional programming is useful. In some other resources, the explanation is very short and vague, such as "pure functions are good", "programs are easier to read", etc. That’s technically true, but it’s difficult for beginners to see how without specific examples. Also, pure functions are good, but how do you structure a program with pure functions (a valid question which beginners will probably have in mind)?

Additionally, some of the techniques of functional programming look really sketchy at first glance, such as monads. For beginners, monads are unintuitive and weird. There better be some very strong motivation if you expect someone to be happy to use those. That motivation should be much better explained than just a vague "purity is good and monads keep programs pure". After all, impure programs are prevalent throughout the industry and they work fine, therefore monads are not necessary.

Further, functional programming is sold like a silver bullet. Don’t get me wrong, it’s helpful, but nowhere near as important as advertised. Applying good programming principles is vastly more important than the majority of techniques in functional programming. Techniques like currying, point-free, function composition, monads, etc., are nice, but they’re not essential for good programs. On the other hand, good programming principles are essential for good programs.

Anyway, rant aside, this article will focus on explaining the motivation for functional programming just as much as the techniques. For each technique, I hope to give you a good understanding of why it’s useful and for why you should use it.

Motivation for functional programming

Functional programming is very useful:

  • It results in better, cleaner and more understandable code.
  • It emphasizes small functions, which are easier to understand, reuse and test. (Although, you should already be using small functions, as explained in clean code and programming principles.)
  • It emphasizes being very careful with side effects, which are difficult to track and can contain bugs. Particularly, it emphasizes immutability, which can make concurrent code much safer to work with. (Again, you should already be doing this, as explained in clean code and programming principles.)
  • And much more.

Overall, it can result in cleaner and less buggy code.

It’s also relatively easy to learn, even though it requires some practice. I recommend starting with just the basics. Then, you can learn more advanced techniques over time.

Prerequisites

To do functional programming, you need a functional programming language. You also need to understand closures.

Language prerequisites

A functional programming language is one that treats functions as "first rate citizens". That means that it allows you to pass a function as an argument to another function, return a function as the result of a function call, etc. Basically it allows you to pass functions around as if they were normal variables.

For example:

function logHello() {
  console.log('hello');
}

function execute(fn) {
  fn();
}

execute(logHello); // 'hello'

In the example, the function execute accepts an argument which is a function. When execute runs, it calls the function it receives as an argument.

Knowledge prerequisites

Functional programming relies heavily on closures. To make your learning journey easier, it’s best that you’re comfortable with them.

If you need more practice, there are many useful resources out there that cover closures.

One of my favourite resources is the course Hard Parts: Functional JS Foundations by Will Sentance. It examines fundamentals like closures, the call stack, scope, local memory in functions and function execution, in great detail. It’s also an introduction to functional programming in general.

I highly recommend watching it, even if you’re an intermediate or advanced developer. Also, you’ll benefit from this course even if you don’t use JavaScript, since most C-based languages are fairly similar.

Target audience

I believe that advanced developers, or possibly intermediate developers, will get the most benefit from this article.

For this article, I would consider a beginner developer to be someone who is still learning how to write basic programs. In this case, getting better at the basics should come first (in my opinion).

Intermediate developers will benefit from learning functional programming. However, in my personal opinion, you’ll get tremendously more benefit from learning programming principles and how to write clean code. In fact, many of the things that functional programming advocates, such as function purity, immutability, declarative programming, etc. are fundamentals and essential techniques for clean code. All programmers must apply them properly regardless of whether or not they care about functional programming.

For advanced developers, functional programming adds some techniques to make your code even better.

Tip – Don’t be afraid to trace through code

Tracing through code refers to manually going through the code, line-by-line, as the computer would. This includes jumping to different function calls and ensuring you know the value of every variable at each line.

Functional programming involves a lot of nested functions, so this process can be quite useful in figuring out how everything works.


Declarative programming

Declarative programming is a frequently used term about functional programming. It’s used both to identify when something follows a functional programming approach, as well as to describe why such as an approach is beneficial.

The common definition of it refers to having your code describe "what" to do, rather than "how" to do it. The Wikipedia page on declarative programming supports this.

To illustrate the difference between "declarative" and "imperative" code, let’s write a function called doubleArray. It will take an array of numbers and return a new array with each number doubled.

Here’s how you could write it using map:

function double(x) {
  return x * 2;
}

function doubleArray(array) {
  return array.map(double);
}

Here’s how you could write it using a for-loop.

function doubleArray(array) {
  const result = [];
  for (let i = 0; i < array.length; i++) {
    const num = array[i];
    const doubled = num * 2;
    result.push(doubled);
  }
  return result;
}

The map way is considered more declarative than the for-loop way. It describes "what" to do, rather than "how" to do it.

This is the kind of explanation you would receive for "declarative programming", which the general definition also supports.

If that explanation makes sense to you, awesome. You don’t need to keep reading this section, you can move onto the next one.

However, if that explanation isn’t enough (it wasn’t enough for me when I was learning), then there is another way to describe it.

The problem is that the map way still seems like "how" rather than "what" (at least to me). It’s saying: "Use the map method of the array, with the function double as the argument."

That seems like an instruction, a "how", rather than a "what".

Further, map itself could very possibly be implemented like this internally:

function map(fn, array) {
  const result = [];
  for (let i = 0; i < array.length; i++) {
    const el = array[i];
    const transformed = fn(el, i, array);
    result.push(transformed);
  }
  return result;
}

I don’t think anyone would dispute that there is a lot of "how" in that code.

So, at best, you can say that the map way has a lot of the "how" code elsewhere. It’s encapsulated in the map method and in the double function, so that the doubleArray function has minimal instructions, minimal "how".

That’s the alternative explanation. Declarative programming isn’t necessarily "what" vs "how", at least not all the way down (considering all of the encapsulated code that you don’t see). It’s just less "how" in the current thing that you’re working on.

On a separate note, code such as this (declarative code) also arises from the application of separation of concerns and DRY / abstraction, which are some of the core programming principles. They advocate having small functions and extracting whatever you can into separate functions, which are ideally reusable, such as map. In other words, whether you care about functional programming or not, following good programming principles naturally leads towards writing declarative code.


Currying

Before we can get into the rest of the goodies of functional programming, we need to briefly cover currying.

Definition of currying

currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument

Wikipedia article on currying

This means that your functions will only ever accept a single argument.

(There are some code examples later in this section.)

Benefits of currying

Currying is a requirement for writing code in point-free style, which can be quite useful.

Currying is also supposed to make it easier to mathematically prove a program’s correctness. However, unfortunately, that doesn’t provide a practical benefit to most programs.

So overall, currying alone doesn’t provide too much of a benefit, but it’s a requirement for writing point-free code, which is beneficial.

Example of currying

To show an example of currying, let’s code a standalone version of the JavaScript string method replace. The function will just invoke the string’s replace method, so it will be a very simple wrapper function.

The first argument will be what substring to replace (replacee), the second argument will be the string to replace it with (replacement), the third argument will be the string to act on (str).

You may notice that the parameter order is a bit unusual. In imperative programming, the "data" parameter tends to come first, because it’s the most important. However, when using currying, it’s placed last to facilitate point-free style. There will be more detail on this in the point-free style section.

Here is a possible implementation without currying:

function replace1(replacee, replacement, str) {
  return str.replace(replacee, replacement);
}

replace1('hello', 'goodbye', 'hello all'); // 'goodbye all'

Here is the implementation with currying:

function replace2(replacee) {
  return function replaceReplacee(replacement) {
    return function replaceReplaceeWithReplacement(str) {
      return str.replace(replacee, replacement);
    };
  };
}

// Or with arrow function syntax
const replace3 = replacee => replacement => str =>
  str.replace(replacee, replacement);

replace2('hello')('goodbye')('hello all'); // 'goodbye all'
replace3('hello')('goodbye')('hello all'); // 'goodbye all'

If you haven’t encountered this before, it may seem confusing at first.

replace2 and replace3 are equivalent. The only difference between them is the syntax. replace2 uses normal functions, while replace3 uses arrow functions (also known as lambda functions), which are just a shorter way to write functions (for the most part).

Here’s how replace2 works:

  1. replace2 is a function which accepts a single argument.
  2. When you call replace2('hello'), it returns a new function named replaceReplacee, which accepts a single argument.
  3. When you call replaceReplacee('goodbye'), it returns a new function named replaceReplaceeWithReplacement, which accepts a single argument.
  4. When you call replaceReplaceeWithReplacement('hello all'), it calls str.replace(replacee, replacement) and returns its result.

The final line (str.replace(replacee, replacement)) works because each variable (str, replacee and replacement) can access its value through the closure mechanism. The variables had their values passed in as arguments earlier.

This is how you can code functions that use currying.

Making currying more user-friendly

In JavaScript (and probably more non-functional-programming languages), the syntax for currying isn’t very user friendly. There is more code to write both when defining the function as well as when calling it.

In some languages, such as Haskell, these issues don’t exist. The syntax for calling or defining a curried function is the same as for calling or defining a function with one parameter.

Thankfully, in a language like JavaScript, these issues can be handled with a curry utility.

Using curry, you can define the replace function like this:

import { curry } from 'ramda';
const replace = curry(
  function replaceUncurried(replacee, replacement, str) {
    return str.replace(replacee, replacement);
  }
);

All you do here is call the curry function with a normal uncurried function, such as replaceUncurried in this case. That way, you can write replaceUncurried as you normally would.

The resulting function can be called in one go (by passing in all 3 arguments), or called any number of times until you’ve passed in all the required arguments. For example, all of these calls are equivalent:

replace('hello', 'goodbye', 'hello all'); // goodbye all
replace('hello', 'goodbye')('hello all'); // goodbye all
replace('hello')('goodbye', 'hello all'); // goodbye all
replace('hello')('goodbye')('hello all'); // goodbye all

So that’s how you can easily use currying in your day-to-day code.

How the curry utility works is not important, but if you’re curious, this is a potential implementation:

function curry(fn, arity = fn.length) {
  // fn.length reads how many parameters the target function has
  return function execute(...args) {
    if (args.length >= arity) { // if there are enough arguments
      return fn(...args);
    }
    return function captureMoreArgs(...moreArgs) {
      return execute(...args, ...moreArgs);
    };
  };
}

That’s pretty much it about currying. However, to make currying actually useful, you need to use it to write code in point-free style.


Point-free

Point-free style can be a cleaner and more concise way to write code. With point-free style, you eliminate the repetition of the "points" (the parameters and their use in the function body) as much as possible.

Point-free examples

Unnecessary wrapper functions

Here are some cases where you would be needlessly creating wrapper functions:

// Example 1
const someAsyncEvent = Promise.resolve();

someAsyncEvent.then(data => console.log(data));
// or
someAsyncEvent.then(data => send(data));

Here is the equivalent code without the unnecessary wrapper functions:

// Example 2
const someAsyncEvent = Promise.resolve();

someAsyncEvent.then(console.log);
// or
someAsyncEvent.then(send);

This is technically point-free style, because you’re not repeating the points (the data parameter). However, really, this version can also be thought of as just removing the redundancy.

In terms of benefits and downsides:

  1. There is clearly more, unnecessary, code in example 1. This makes it slightly less clean.
  2. More code = more bugs. Example 1 creates new, untested, wrapper functions. There is a low chance that somehow they’re coded incorrectly. In this case, you would have a bug.

Of course, overall, the practical differences between the examples are minor.

Replace example

Let’s examine another example. Imagine that in your codebase you frequently need to replace the substring "hello" with "goodbye".

// Example 1
const str1 = 'hello Bob'.replace('hello', 'goodbye'); // 'goodbye Bob'
const str2 = 'hello Alice'.replace('hello', 'goodbye'); // 'goodbye Alice'
const str3 = 'hello everyone'.replace('hello', 'goodbye'); // 'goodbye everyone'

As a programmer, you know about abstraction and DRY. A lot of repetition is harmful. To fix that, you refactor the common parts of the code into a function:

// Example 2
import { replace } from 'replace';

function replaceHelloWithGoodbye(str) {
  return replace(str, 'hello', 'goodbye');
}

const str1 = replaceHelloWithGoodbye('hello Bob'); // 'goodbye Bob'
const str2 = replaceHelloWithGoodbye('hello Alice'); // 'goodbye Alice'
const str3 = replaceHelloWithGoodbye('hello everyone'); // 'goodbye everyone'

The replace function that’s imported would be defined like this:

function replace(str, replacee, replacement) {
  return str.replace(replacee, replacement);
}

One thing you may be wondering is why import the replace function instead of quickly defining it from scratch? This is explained a bit later in this section.

Back to the point, example 2 is an improvement over example 1, because there is less repetition. However, using currying, the code can be improved even more.

The issue is that there is still repetition in the code. Specifically, the str variable appears twice, once as the parameter of replaceHelloWithGoodbye and the second time when it’s used in the function body of replaceHelloWithGoodbye.

In the spirit of extreme DRY and more concise code, the duplication can be removed like this:

// Example 3
import { replace } from 'ramda';

const replaceHelloWithGoodbye = replace('hello', 'goodbye');
const str1 = replaceHelloWithGoodbye('hello Bob'); // 'goodbye Bob'
const str2 = replaceHelloWithGoodbye('hello Alice'); // 'goodbye Alice'
const str3 = replaceHelloWithGoodbye('hello everyone'); // 'goodbye everyone'

Here, replace is the same as the curried version from earlier:

import { curry } from 'ramda';
const replace = curry(
  function replaceUncurried(replacee, replacement, str) {
    return str.replace(replacee, replacement);
  }
);

In this example, the str parameter and its usage have been completely removed from replaceHelloWithGoodbye.

This makes this version slightly more concise and cleaner.

Everything else is the same.

Benefits of point-free

As you saw in the examples, point-free style provides some minor benefits.

Less code

The point-free versions have less code due to not repeating the points (function parameters). This makes them slightly cleaner than the alternatives.

Additionally, less code = less bugs. There is less chance of making a mistake in some redundant code and breaking the program.

Encourages reusable functions

Using point-free style requires a function that can be curried. It’s unlikely that you’ll code this function from scratch every single time. Most likely, you’ll either import it from a library, or code it once in the codebase and then import it every time you need it.

This is good, because if you only code a function once, you can test it very thoroughly that one time. This reduces the chance of errors and it keeps code "dry".

On the other hand, without currying, it’s very easy to just redefine a function every time you need it. For example, in example 2, you could have coded:

function replaceHelloWithGoodbye(str) {
  return str.replace('hello', 'goodbye');
}

Overall, this is worse, as you might make a mistake sometime. Also, you probably won’t thoroughly test the function every single time you redefine it. Finally, you’re repeating yourself and the points all throughout the codebase.

The benefits are small

But overall, the differences are very small.

Even with the benefits we’ve discussed already, point-free style or not probably won’t make a significant difference to your codebase.

At the end of the day, you have to use your own judgement and decide whether applying this style in your codebase would be beneficial.

You may decide that the discomfort of your team to learn and apply the new style is not worth it and that’s fine.

Using point-free style

There are two things you can do to use point-free style in your codebase:

  1. Use a library which provides curried functions.
  2. Create your own curried functions.

Library of curried functions

A functional programming library like Ramda provides many curried functions for you to use. They are similar to the replace function that was used in the examples.

For example:

  • add
  • always
  • map
  • filter
  • reduce
  • any
  • cond
  • invoker
  • equals

There are many more.

For the most part, these are either simple, curried functions (like add), or simple wrapper functions of the corresponding method in JavaScript (like map).

Creating your own point-free functions

You can also easily create your own functions suitable for point-free style.

You do this by listing the function parameters in order of least importance to most importance. For example, the data to operate on is the most important parameter, so it’s usually the last parameter of the function.

This tends to be the reverse of imperative programming, where the most important parameters tend to come first.

Finally, both in imperative functions and function with currying, if some parameters have roughly equal importance, it’s fine to list them in the order you find more natural.

For example, in the replace function, the parameters replacee and replacement have roughly equal importance. In this case, you can order them however you want. However, it’s good to follow conventions (this applies the principle of least astonishment), so normally, replacee should come first since that’s the standard in many programming languages.

Here’s the point-free replace function for reference:

import { curry } from 'ramda';
const replace = curry(
  function replaceUncurried(replacee, replacement, str) {
    return str.replace(replacee, replacement);
  }
);

If you need additional convincing that the data parameter should come last, examine the replace and replaceHelloWithGoodbye functions a bit more. Consider if it would be possible to use point-free style if the str parameter wasn’t last. Also consider what particular use-cases you may have in your codebase. How likely is it that you’ll want to provide the str argument before the replacee or replacement arguments?

For additional examples of curried functions, their implementations and their parameter order, I recommend checking out the documentation (and possibly source code) of a functional programming library like Ramda.

Also, if you want the practice, try to code some curried functions yourself. You can use a process like this:

  1. Look at the documentation of a functional programming library and find the list of functions it provides.
  2. Pick a function.
  3. Try coding it.
  4. Check yourself against the actual implementation (see it in the library’s source code).
  5. Repeat with another function.

Summary of point-free

Point-free style just applies DRY. It allows you to not repeat function parameters multiple times, and therefore make your code a bit cleaner.

Less code = less bugs.

Also, point-free style encourages you to reuse existing utility functions, rather than recreate them from scratch each time, which makes the code safer.

But realistically, the benefits are very minor.

Be pragmatic and do whatever is best for your codebase. Don’t feel too guilty if you decide that the minor benefits of point-free are not worth the discomfort of learning and applying them in your codebase.


Function composition

Continuing on with DRY and abstraction, let’s examine function composition.

Simple function composition

Consider a function like this:

// Example 1
function foo(nums) {
  const doubledNums = doubleArray(nums);
  const price = calculatePrice(doubledNums);
  const priceWithVAT = addVAT(price);
  return priceWithVAT;
}

foo([1, 2, 3, 4, 5]);

While the example is made up, this kind of pattern is fairly common in real codebases.

You call a function and store the result. Then you call the next function with the stored result and store the new result. You repeat this process until you return the final result.

This is fine, but you can use function composition to make the code more concise:

// Example 2
import { compose } from 'ramda';

const foo = compose(addVAT, calculatePrice, doubleArray);

foo([1, 2, 3, 4, 5]);

compose calls the functions from right to left, getting the result each time and passing it as the argument to the next function in the chain. At the end, it returns the final result.

Example 2 is equivalent to example 1. I personally consider it significantly cleaner.

If the function order seems unusual, you can use the chain function instead. It’s similar to compose, except that the order of functions goes from left to right.

// Example 3
import { chain } from 'ramda';

const foo = chain(doubleArray, calculatePrice, addVAT);

foo([1, 2, 3, 4, 5]);

Complex function composition

In addition to that simple case, there are many more use-cases that show up in real codebases.

For example:

  • Exceptions
  • If statements
  • Diverging, then converging code paths. E.g. running two different functions and then combining the results of each.
  • Functions with side effects
  • Etc.

You can use function composition for all of those cases. You just need to use the correct function to handle them.

For example Ramda provides a cond function which is basically an if, else if, else operator.

import { cond, equals, always, compose, modulo, partialRight, T } from 'ramda';

// Example 1 with imperative code
function foo1(num) {
  if (num === 0) {
    return 'zero';
  }
  if (num % 2 === 0) {
    return 'even';
  }
  return 'odd';
}

// Example 2 with functional point-free code
const foo2 = cond([
  [equals(0), always('zero')],
  [compose(equals(0), partialRight(modulo, [2])), always('even')],
  [T, always('odd')],
]);

// Example 3 with functional code and isEven extracted
const isEven = x => x % 2 === 0;
const foo3 = cond([
  [equals(0), always('zero')],
  [isEven, always('even')],
  [T, always('odd')],
]);

So with function composition, you can code conditionals, or anything else you want.

On a separate note:

I’m not ashamed to admit that, even though I wrote the code, I find foo2 very hard to read and understand. I find example 3, which uses a simpler version of isEven, much easier to read.

But overall, personally, I find example 1 the easiest to read and understand. Maybe it’s familiarity, but I can instantly tell what’s going on in example 1 with no issue. Additionally, example 1 looks just as clean to me as the other examples, probably because each line is doing one thing, compared to examples 2 and 3 where each line is doing multiple things.

Overall, I would personally prefer to use example 1 in a codebase that I was working on.

If you really wanted to apply separation of concerns and DRY to the extreme, you might extract some functions from example 1, such as isEven and isZero. This may make the code longer and make it feel less clean, which might make example 3 the better choice.

But anyway, the point is, remember to be pragmatic. Use the code that’s best for your codebase. Don’t just dogmatically apply practices and principles that make your code worse. Your goal is to write the best software that you can, not to apply functional programming dogmatically.

But also, back to the main point, if you want to do everything with function composition, you can. There are functions to handle everything that you may encounter in imperative programming.


Immutability

Functional programming promotes immutability.

I’m not going to cover this point too heavily here. If you’re using good programming principles, you should be using immutability anyway (as long as performance isn’t an issue, which most likely it won’t be). Immutability applies the principle of least knowledge and reduces the chance that changing a value here will break something unrelated elsewhere. This is covered in more detail in the post clean code and programming principles.

However, as a quick summary, immutability:

  • Prevents the majority of unnecessary side effects.
  • Makes code easier to read, track and understand, as it gives guarantees about the values of variables, so you don’t have to hunt through the codebase to try and track their values.

For more information on immutability, please see the relevant section on clean code and programming principles.


Function purity

Similar to immutability, this is also a point I’m not going to cover heavily here. All programmers should know that side effects are dangerous, not just people interested in functional programming.

As a result, this is a topic I’ve covered in the post clean code and programming principles.

In summary:

  • It’s important to write pure functions whenever possible. That’s a requirement for separation of concerns and the principle of least knowledge.
  • You should be extremely careful when writing code with side effects, such as mutating the DOM, the database, etc.

There are some more things to keep in mind, but for the most part, you just have to be extremely careful. Side effects are dangerous and functional programming is not a silver bullet against them. You still have to handle them just as much as with imperative programming. Even when using functional programming techniques such as laziness, you can still code impure functions incorrectly, call them in the wrong order, or have them access things which don’t exist.

So, for more information on how to handle side effects and about function purity, please see the post clean code and programming principles.


Monads

Let me start out by admitting that I don’t have a lot of experience with monads. As of the time of writing, I haven’t used them professionally. At most I’ve tried them a bit in personal projects.

Even so, I believe that I see some of the motivation behind them, so I hope I can help you do the same.

In this section I’d like to:

  1. Describe monads very briefly.
  2. Show you some cases where they’re useful.

After that, you can decide if they’re worth learning further. Then you can go learn them from a more thorough resource if you’re interested.

About monads

Monads are basically containers for values.

Let’s look at some examples that are similar to monads.

A class with only private values

Here is a simplified version of what a monad looks like internally:

class MonadLikeClass {
  constructor(value) {
    this.value = value;
  }

  map(fn) {
    this.value = fn(this.value);
  }
}

(Pretend we’re using an OOP language which has private properties. Here, this.value is intended to be private and not accessible from the outside.)

Additionally, monads are immutable, but for the sake of simplicity, let’s ignore that in this example.

Essentially, MonadLikeClass is, for the most part, a container for a value.

The value is private, so it’s never accessible from the outside.

To change the value, you have to call the map method with a callback. The callback will receive the current value, operate on it and return a new one. Then, the old value is replaced with the new value.

That’s pretty much it for a basic monad.

For example, here is how you might double the value inside the monad.

function double(x) {
  return x * 2;
}

const myMonad = new MonadLikeClass(500);
myMonad.map(double); // myMonad now contains the value 1000

JavaScript promise example

If you use JavaScript, you’ve probably used promises. A promise is essentially the Task monad (except for some very minor, technical differences).

So, as with other monads, a promise is a container for a value. That value is not accessible from the outside. To do things with that value, you must call the promise’s then method (similar-ish to map for normal monads) with a callback.

In addition, promises also encapsulate some code to make asynchronous programming easier. This additional code is what makes each type of monad useful in different circumstances.

Here’s some example code using a promise:

function double(x) {
  return x * 2;
}

const someAsyncTask = Promise.resolve(1);
someAsyncTask.then(double); // returned promise contains the value 2

In this example, someAsyncTask immediately resolves to 1. As a result, the promise someAsyncTask holds the value 1 inside it.

But you can’t access 1 outside of the promise. Instead you call .then and pass in a function that the promise (container) will run on its value. That function is the only thing that receives the value.

After all that, you don’t get the final value out. Instead, you get another promise, which contains the new value.

So, to summarize one last time: Monads are containers of values. To do work with those values, you pass in functions, then a new monad is returned with the new value. The values are not normally accessible from the outside.

Motivation for monads

So far we’ve only talked about the "container" property of monads. However, that’s not what makes them useful. They would be useless if they didn’t do anything else.

The benefits come from other functionality that monads also contain.

Benefits of promises

For example, consider JavaScript promises again.

They are useful because they contain a lot of code which helps with asynchronous programming.

Promises:

  • Prevent callback hell.
  • Provide a good structure for writing asynchronous programs.
  • Encapsulate a lot of code which makes sure they can only be resolved once.
  • And more.

Without promises, here is some code you might write to ensure that a function can only be called once:

function onlyOnce(fn) {
  let hasBeenCalled = false;

  function execute() {
    if (!hasBeenCalled) {
      hasBeenCalled = true;
      return fn();
    }
  }
  return execute;
}

function logHello() {
  console.log('hello');
}

const logOnlyOnce = onlyOnce(logHello);
logOnlyOnce(); // hello
logOnlyOnce(); // (nothing happens)
logOnlyOnce(); // (nothing happens)

The benefit of promises is that they contain a version of this code, internally, already.

This saves you from having to write and manage this code yourself, which is very helpful.

So overall, promises are not essential. Before promises, we could still do any kind of asynchronous programming we wanted to in JavaScript. However, they are very useful.

Other monads are equally useful.

Option / Maybe monad

Let’s examine the motivation for the Option or Maybe monad.

Consider any function whatsoever in your codebase that sometimes returns null.

As an example, consider a function called getNextScheduledEvent that returns the user’s next scheduled event.

function getNextScheduledEvent(user) {
  if (user.scheduledEvents.length > 0) {
    return user.scheduledEvents[0];
  }
  return null;
}

Sometimes this function returns null. This is intended behaviour, it’s not a bug. The user may not have any scheduled events and that’s valid.

Ultimately, this means that somewhere, later on, in the codebase, you’ll need an if statement to check for null.

If you forget the null check, that’s a bug. Your program may crash or do the wrong thing as a result.

The problem is that it’s easy to forget. At other times, you might just not know in the first place. Maybe you’ve never worked with the module in question so you have no idea you need a null check here and another one there.

Consider: How are you supposed to know or remember that the value returned from the function foo on line 300 of file importantCalculations.js might have returned null and it’s reached all the way here and if you forget to check for null you’re going to crash your program?

So one option for dealing with null checks is: just don’t forget.

Another option, is to use a type system that warns you at the compiler level that something could be null and forces you to check for it. This is one of the benefits that TypeScript provides with its strictNullChecks option.

Yet another option, is to use the Option or Maybe monad. This monad includes a null check inside it. This automatically handles the null checks for you, so it’s impossible to forget to code them and crash your program.

This is very helpful.

Here is a simplified version of the Maybe monad.

class Maybe {
  constructor(value) {
    this.value = value;
  }

  map(fn) {
    // always existing null check
    if (this.value) {
      const result = fn(this.value);
      return new Maybe(result); // return a new monad with the new result to keep things immutable
    }
  }
}

Here’s how you could use it:

function toUpperCase(str) {
  return str.toUpperCase();
}

function getNextScheduledEvent(user) {
  // Instead of returning null sometimes and a proper value at other times, always return a Maybe
  if (user.scheduledEvents.length > 0) {
    return new Maybe(user.scheduledEvents[0]);
  }
  return new Maybe(null);
}

// User 1 with no scheduled events
const user1 = {
  scheduledEvents: [],
}
const maybeEvent1 = getNextScheduledEvent(user1);
maybeEvent1.map(toUpperCase); // nothing happens, program doesn't crash, null check is included inside Maybe

// User 2 with scheduled events
const user2 = {
  scheduledEvents: ['Awesome event'];
}
const maybeEvent2 = getNextScheduledEvent(user2);
const upperCasedMaybeEvent2 = maybeEvent2.map(toUppderCase); // contains 'AWESOME EVENT'

Now, you can never forget to include the required null check, because it’s included automatically.

In addition, you also save on code, because the null check is only coded one time inside the monad, rather than hundreds of times all over the codebase.

So hopefully, this shows you how this monad is useful.

More monads

Other monads are just as useful in other ways. I’ll leave them up to you to research further when you decide to take the plunge.

Summary

To summarize:

  • Just like with many things in functional programming, you don’t need monads. However, they can be very useful.
  • All monads are containers for values. The values are private and aren’t accessible from the outside. You call the monad’s map method with a function to operate on the value. The old value is overwritten with the new value, or, if you’re keeping things immutable, a new monad is returned with the new value and the old monad is left untouched.
  • Monads also contain additional code in their map method which does useful things. For example the Maybe monad always checks if the value is null before trying to run your provided function.

There are many more things you need to know to work with monads effectively. If you’re interested in learning more, there are many good resources on them. I’ve provided some recommendations at the end of this article.

While you’re at it, remember to be pragmatic. Monads are not necessary, they are just useful. If you don’t want to use them, you don’t technically have to, especially if, say, the majority of your team is not comfortable with them.

But, at the very least, it’s always good to practice using them. That way, you can actually use them if you want to. You can also make a more informed decision as to whether or not they’re worth using.


Functional reactive programming

Functional reactive programming is another topic that’s very useful to know.

It’s particularly good for working heavily with things that can be modelled as streams.

Streams are asynchronous data or events, where order is (often) important.

To better explain that, let’s start simple and build up to something you might model as a stream.

Synchronous, single value

Here is something that’s definitely not a stream: A synchronous operation that you only need to do on one value.

function double(x) {
  return x * 2;
}

const initialValue = 5;
const result = double(initialValue);

Synchronous, list of values

Next is a synchronous operation that you do on each element of a list of values.

function double(x) {
  return x * 2;
}

const initialList = [1, 2, 3, 4];
const doubledList = initialList.map(double);

Asynchronous, single event

Next, consider an asynchronous operation, which is fine to run any time an event occurs. In other words, an asynchronous operation which works on a single value at a time without caring about when it will be run again and with what value. A completely independent asynchronous operation.

function handleFormSubmit(event) {
  event.preventDefault(); // prevent the form from submitting so we can handle behaviour with JavaScript
  const data = event.target.querySelector('input').value;
  const doubledData = double(data);
  console.log(doubledData);
}

const target = document.querySelector('#myForm');
target.addEventListener('submit', handleFormSubmit);

Asynchronous, multiple events

The final and most tricky situation, is when you need to work asynchronously with data or events that depend on previous data or events.

To illustrate this, consider this example:

You provide an input for the user, so they can type a search term. As the user types, you read their search term and use it to submit a network request to get relevant information from an API. Then you display the results on the DOM for the user to see.

Here is an example implementation:

function handleInput(event) {
  const searchTerm = event.target.value; // get the search term
  fetch(`https://myfakeapi.com?q="${searchTerm}"`) // make the HTTP request
    .then(res => res.json()); // access the response
    .then(displayResultsOnTheDom); // write the results to the DOM
}

const target = document.querySelector('#userInput');
target.addEventListener('input', handleInput); // set up an event on the input

This… kind of works… But it might not be enough.

There are more things that may need to be handled:

  • You might not want to perform a search until the user stops typing for some time, say, 0.5 seconds. If you don’t do that, then you could be creating multiple network requests per second if the user types quickly. This is unnecessary and could be bad for performance and the user’s internet usage. Therefore, you want to "debounce" the events and only handle them every, say, 0.5 seconds.
  • What if the user types the same search term in a row (possible by selecting everything, copying, and then pasting and overwriting the whole thing)? You don’t want to perform another network request unless the search term is different.
  • How do you handle race conditions? With the current implementation, it’s not guaranteed that the results from the last network request will be the ones shown to the user. Rather, the results from the slowest network request may be shown, the results that arrive last, rather than the ones with the latest search term.

In a real example, there would be different and / or more cases.

Coding the solutions to those things is quite the nightmare…

Here is a potential implementation:

// Utility functions
function debounce(fn, delay) {
  let timeoutId = null;

  return function execute(...args) {
    clearTimeout(timeoutId);
    const toCall = () => fn(...args);
    timeoutId = setTimeout(toCall, delay);
  }
}

function distinct(fn) {
  let lastValue = 'somethingUnique';

  return function execute(arg) {
    if (arg !== lastValue) {
      lastValue = arg;
      return fn(arg);
    }
  }
}

function onlyLatestPromise(onResolve, onReject) {
  let latestPromiseId = 0;

  function runLatestPromise(promise) {
    latestPromiseId++;

    const runIfLatestPromise = capturePromiseId(latestPromiseId);
    promise.then(runIfLatestPromise, onReject);
  }

  function capturePromiseId(promiseId) {
    return function runIfLatestPromise(res) {
      if (promiseId === latestPromiseId) {
        onResolve(res);
      }
    };
  }

  return runLatestPromise;
}

// Custom code

function getSearchTerm(event) {
  return event.target.value;
}

function makeRequest(searchTerm) {
  return fetch(`https://myfakeapi.com?q="${searchTerm}"`);
}

function handleResponse(res) {
  return res.json()
    .then(displayResultsOnTheDom);
}

// Join everything up

const onlyLatestRequest = onlyLatestPromise(handleResponse);

const getAndHandleRequest = searchTerm => {
  const promise = makeRequest(searchTerm);
  return onlyLatestRequest(promise);
}

const handleDistinctSearchTerm = distinct(getAndHandleRequest);

function handleInput(event) {
  const searchTerm = getSearchTerm(event);
  handleDistinctSearchTerm(searchTerm);
}

const debouncedHandleInput = debounce(handleInput, 500);

// Set up the event

const target = document.querySelector('#userInput');
target.addEventListener('input', debouncedHandleInput);

That is a lot of code. It’s quite messy and it’s very difficult to understand.

Alright, so at least the functions debounce, distinct and onlyLatestPromise are utility functions. Perhaps they can be imported from a library.

Some of the other functions like getSearchTerm, makeRequest and handleResponse are custom functions that we definitely need. These are fine as well.

But the remaining functions, such as onlyLatestRequest, getAndHandleRequest and so on, are just weird and awkward. They are so messy, so hard to follow and they need to be carefully and sequentially joined up, otherwise the code won’t work.

As an alternative to this approach, we could have a god function that does everything inside it. That would save on some code and the awkwardness of connecting everything together, but obviously it wouldn’t be any better.

In comparison, here’s what the code would look like if we used a functional reactive programming library such as RxJS:

import { fromEvent } from 'rxjs';
import { map, switchAll, debounceTime, distinct } from "rxjs/operators";

function getSearchTerm(event) {
  return event.target.value;
}

function makeRequest(searchTerm) {
  return fetch(`https://myfakeapi.com/?q="${searchTerm}"`);
}

function handleResponse(res) {
  res.json()
  .then(displayResultsOnTheDom);
}

const target = document.querySelector('#userInput');
const inputEvents$ = fromEvent(target, 'input');

inputEvents$.pipe(
  debounceTime(500),
  map(getSearchTerm),
  distinct(),
  map(makeRequest),
  switchAll(), // waits until the latest promise resolves, and afterwards passes the response
  map(handleResponse)
).subscribe();

With RxJS, we don’t need to code the utilities ourselves. RxJS provides them all.

We still have our custom functions like getSearchTerm just like before.

As for joining everything up, that’s done in the inputEvents$.pipe section. That is far better than the custom implementation where we joined up all of those functions by hand. This time they are much simpler to read and understand.

So this is an example where a functional reactive programming library is really useful and saves a lot of time.

As with everything else, it’s not necessary. However, the difference is significant. If you’re working with things that can be modelled as streams, a functional reactive programming library can save you a lot of time and make your code much cleaner.

What is a stream?

A stream is a collection of stuff (data, events, or anything else) that are asynchronous (arrive over time) and that often need to be processed in order.

In terms of using reactive functional programming: It’s useful to model things as streams that are asynchronous and that depend on previous data or events. For example:

  • The debounce function needs to remember when the last "activation" was.
  • The distinct function needs to remember what the previous piece of data was.

Basically, anything that requires functionality such as those functions may be useful to model as a stream.

More examples include whenever you need the functionality provided by a reactive functional programming library such as RxJS, such as:

  • Throttle
  • Debounce
  • Distinct
  • Race
  • skip
  • takeUntil
  • retry
  • And many more

Further reading

So that’s an overview of the basics of functional programming. We didn’t really cover details, only the basic concepts of how functional programming works.

In the process, I hope you saw some of the motivation for using functional programming.

Next, if you’re still interested, I recommend continuing your learning. You can get more practice with the basics covered here by learning from a much more thorough resource. There are also many more things I didn’t cover at all, such as cool ways to use list operations, transducing, recursion, and more.

For further reading, I strongly recommend the following resources:

If you consider yourself a beginner at functional programming or programming in general, I recommend the course Hard Parts: Functional JS Foundations by Will Sentance. He explains the fundamentals of functional programming very well. The course stays fairly basic, but I believe it’s very good and very important to grasp the concepts in this course before progressing further. If you’re not a beginner, you may not need this course, but if in doubt, I highly recommend it.

If you’re not a beginner programmer, and want a very thorough introduction to functional programming, I highly recommend Functional-Light JavaScript by Kyle Simpson. If you’re more of a visual learner, Kyle Simpson also has a video course called Functional-Light JavaScript, v3 covering the same topics. This was the book which finally taught me functional programming. It’s very thorough and it covers most of what I know about functional programming today except for monads.

Finally, if you want to take it a step further, then maybe try out Professor Frisby’s Mostly Adequate Guide to Functional Programming. Even though I haven’t finished this book yet, I’ve done parts of it and it looks quite thorough. Additionally, colleagues that I trust also recommend this book. This book is lighter and faster on the basics of functional programming, so bear that in mind if you want something thorough on those topics. However, it covers monads very thoroughly. In fact, the majority of the book is focused on monads.

Alright, I hope you enjoyed this article and hopefully got some value out of it.

If you have any comments, suggestions, differences in opinion or anything you want to share, please feel free to leave a comment.

See you next time.

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments