Functional programming - The ultimate beginner's guide

Functional programming – The ultimate beginner’s guide

In this post, I want to teach you a few things:

  • The practical benefits of functional programming, from a programmer’s point of view.
  • A quick overview of how to actually use a few important concepts in functional programming.

Love-hate relationship

To start with, let me start off by saying that I absolutely love functional programming.

I love all the practical benefits it brings to code.

It results in better, cleaner, more understandable code, with less bugs. And it’s honestly not very difficult. It takes some learning and some dedication, but that’s really no different from when you learned programming in the first place.

But, there are also some things I dislike about functional programming. I dislike how functional programming is taught, almost everywhere. I also dislike how functional programming is being sold as a silver bullet, that’s going to solve all your problems with programming. Finally, I really dislike statements like "forget everything you think you know about programming", which are completely absurd.

How functional programming is being taught

Disclaimer: This is just my experience. Your experience may be different. However, I’ve tried to learn functional programming a few times, read a lot of resources about it, spoke to many people about it and only found a few resources that did not follow this pattern.

Perhaps it’s just my experience, but the majority of resources hardly explain any practical benefits of functional programming.

Most of the time, the explanation is something along the lines of "and here is what lambda calculus is so let’s do that with code". As for why we might want to do that at all, there’s hardly an explanation. It’s like the motivation is the mathematics, and that’s it.

When there is an explanation, it’s along the lines of "pure functions are good", without any more explanation than that.

For example, one of the examples I dislike, which I’ve seen multiple times, is this:

"We can make a function pure by using laziness."

And this tends to be the end of the explanation. There’s no additional explanation for why that may be beneficial.

Here’s the code:

function foo() {
  function bar() {
    return document.querySelector('foo');
  }
  return bar;
}

Technically, foo is pure because it always returns the same thing, a function named bar

If you’re new to functional programming, you’re probably thinking: "That seems pretty useless. So what if we wrapped bar in a function to make it lazy? It’s not like that provides much practical benefit in our program."

For the most part, you’re right. Adding laziness has some benefits which we’ll examine later, but it doesn’t make our code safe by a long-shot.

bar is still going to run at some point. If bar is coded incorrectly, we have a bug. If bar runs in the wrong order, we have a bug. If bar grabs a value from the DOM that’s not what was expected, we have a bug. Adding laziness does not remove any of those problems.

So anyway. That’s one of the main reasons for this post, to try to explain the practical benefits of functional programming from a programmer’s point of view.


Language prerequisite – Functions as first-rate-citizens

The only prerequisite for being able to do functional programming in your programming language is the ability to pass a function as an argument to another function.

For example:

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

function execute(fn) {
  fn();
}

execute(logHello); // 'hello'

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

With that prerequisite out of the way, let’s move on.


Your preparation

To tackle this article, I recommend that you first spend some time refreshing your knowledge about closures. You’ve probably used closures a lot already, but because they’re relied upon a lot in functional programming, it’s good to make sure that you understand them and that you’re comfortable with them.

Additionally, be prepared to do a lot of tracing of code, just like when you first learned programming. Functional programming has function calls left and right, which require code tracing until you get comfortable with them. This goes doubly so for recursion.

But it’s not too bad. As long as you persevere, you’ll be okay.

So, with all that out of the way, let’s start.


Declarative programming

What is declarative programming?

What vs how?

Everything I’ve ever read and everyone I’ve ever spoken to always answers like this: "Write your code to say what you want, not how to do it".

If that makes sense to you, that’s great. Please feel free to skip this section.

As for me, I never really understood it when phrased like that.

For example, some people use the function map to illustrate what they mean by what vs how.

Let’s examine that. Let’s see how we would write some code using map (the declarative way) vs using a for loop (the imperative way).

We’ll write a function that takes an array as an argument, and returns a new array with every element doubled.

Here is how we would do it with 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;
}

Here’s how we could do it with map, the "declarative" way:

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

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

Maybe I’m just slow with this, but I can’t really see a "what" in there. It seems to me that we’re still saying:

"Hi array, run your map method, with double as the function".

That seems a lot like "how" to me.

Sure the code is smaller, and there is less "how" in this example, but still, I don’t see any "what".

Furthermore, here is how map probably looks behind the scenes. It probably uses loops, considering JavaScript doesn’t support recursion with tail-call optimisation at the time of writing:

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.

Declarative = applying programming principles

What made a lot more sense to me, however, was ignoring "how" vs "what" and just using good programming principles.

Principles like:

  • Single responsibility principle.
  • Separation of concerns
  • DRY
  • Abstraction
  • Etc.

When we use map, we’re just moving the for-loop somewhere else.

This is good, for many reasons.

DRY

One of the basic principles of programming is DRY (don’t repeat yourself).

Instead of having for-loops that do almost exactly the same thing every time, we can create a function that does the for-loop and accepts arguments for the differences. This results in the functions map, filter, forEach and reduce.

Separation of concerns and single responsibility principle

Our original doubleArray function did a lot of things:

  1. It created a new array to hold the result.
  2. It created a variable i.
  3. It increments i due to the for-loop.
  4. It got the element at index i.
  5. It doubled the element.
  6. It pushed the doubled element to the result array.
  7. At the end of it all, it returned the result.

That’s a lot.

In programming, we like our functions small. We don’t want them to do too much.

Among other things, smaller functions are:

  • Easier to read and understand.
  • Easier to change, and changes are safer.
  • Easier to reuse.
  • Easier to test.

We make functions smaller by extracting functionality into separate functions.

The result is map. Instead of the imperative doubleArray, we apply separation of concerns and extract what we can. The code for doubling becomes a separate function double, and the code for the for-loop becomes a separate function map. Our doubleArray function then becomes a very simple function which uses double and map.

This extraction of functionality is also known as making our code more declarative. In other words, there is less "how" in our function because we extracted the "how" into separate functions.

Summary

Declarative programming can be thought of as the application of good programming principles. We want to extract functionality from large functions into separate functions, so that our functions are small. We also don’t want to repeat ourselves, so we refactor common functionality into reusable functions.

You can also think of it as having less "how" in your functions, by extracting functionality into separate functions.

On a separate note, these are things we should be doing even without functional programming. We just need to apply good programming principles.


Currying

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

I don’t believe that currying provides much of a benefit when used on its own, but it’s a requirement for writing point-free code, which does have benefits. Technically, it’s also supposed to make it easier to mathematically prove a program’s correctness, but unfortunately, that doesn’t really provide a benefit to most developers.

To show what currying does, let’s use a contrived example. We’ll code a standalone version of the JavaScript string method replace. Our 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 what to replace it with (replacement), the third argument will be the string to act on (str). Don’t worry if the order of the arguments seems weird for now.

Here is how we might code the function:

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

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

replace1 is just a normal function which accepts 3 arguments and then executes.

Here is how we would code the function using currying:

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

// 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 look confusing at first.

Both replace2 and replace3 work the same way. They just use different syntax to write the same thing. replace2 uses normal functions, while replace3 uses arrow functions (also known as lambda functions), which are just a shorter way to write an anonymous function.

Here’s how replace2 works:

  1. replace2 is a function which accepts a single argument.
  2. When we call replace2('hello'), it returns a new function named replaceReplacee, which accepts a single argument.
  3. When we call replaceReplacee('goodbye'), it returns a new function named replaceReplaceeWithReplacement, which accepts a single argument.
  4. When we 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 have the values passed in as arguments earlier.

This is how we code functions that use currying.

However, in JavaScript, the syntax isn’t very user friendly. There are two issues:

  1. The first issue is all the extra code we have to write. When defining the function, we have to code functions which return functions which return functions, repeatedly. That’s because each function can only accept a single argument, so this is one way to make that work.
  2. The second issue, is that the function call itself is longer. Instead of calling replace1('hello', 'goodbye', 'hello all'), we have to write replace2('hello')('goodbye')('hello all').

In some languages, such as Haskell, these issues don’t exist. The syntax is the same for calling or defining a single function, as for calling or defining multiple functions.

Thankfully, in a language like JavaScript, we can handle both of these issues using a curry utility.

Using curry, we can define our replace function like this:

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

All we’ve done here is pass our replaceUncurried function to the curry function.

This allows us to write our replaceUncurried function as we normally would. It also allows us to call the resulting replace function in any way we like. For example, all 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 we can use currying in our day-to-day code, without it being an issue to use.

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 our target function has
  return function execute(...args) {
    if (args.length < arity) {
      return function captureMoreArgs(...moreArgs) {
        return execute(...args, ...moreArgs);
      };
    }
    return fn(...args);
  };
}

That’s it about how currying technically works.

But to actually make currying useful, we need to use it to write code in point-free style.


Point-free

A nice benefit we can gain from functional programming is the ability to write in point-free style.

Point-free style is where we don’t repeat the "points" (parameters) in function definitions.

Point-free examples

Unnecessary wrapper functions

Here are some cases where we’re creating wrapper functions for absolutely no benefit:

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

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

When the exact same code can be written without the wrapper functions like so:

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

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

This is technically point-free style, because we’re not repeating the points (the data parameter). But 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. More code = more bugs.
  2. Example 1 is very slightly less safe, because the inline functions we’ve coded are brand new and probably untested, compared to the functions console.log and send which have probably been thoroughly tested elsewhere. In the rare case that our wrapper function was coded incorrectly, we have a bug.

The only possible benefit of example 1 may be to people unfamiliar with how promises work. Seeing the flow of the arguments may be helpful and provide clarity. However, after getting used to how promises work, this is no longer necessary.

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

Replace example

Let’s examine another contrived example. Consider that in our codebase we 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 programmers, we know about abstraction and DRY. We shouldn’t repeat ourselves like that. To fix that, we refactor the common parts of the code into a function:

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

const replaceHelloWithGoodbye = str => 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 we imported would be defined like this:

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

One thing you may be wondering is: Why did we import the replace function in example 2? Couldn’t we have just written const replaceHelloWithGoodbye = str => str.replace('hello', 'goodbye')? We’ll examine that later, but for now, please ignore this point.

Example 2 is an improvement, because we’re not repeating ourselves as much.

But with currying, we can take DRY a small step further.

The issue in example 2 is that we’re still repeating ourselves in the code. Specifically, we’ve written the str variable twice. Once as the parameter of replaceHelloWithGoodbye, and the second time when we use it in the function body of replaceHelloWithGoodbye.

In the spirit of extreme DRY and more concise code, we can code it like this to remove the duplication:

// 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 we saw earlier:

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

In this example, we’ve removed the str parameter from the replaceHelloWithGoodbye function.

This makes this version slightly more concise and cleaner.

Everything else is the same.

Benefits of point-free

As we saw in the examples, point-free style provides a few (small) 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 that we might make a mistake in some redundant code and break our program.

Encourages reusable functions

The point-free requires a function that can be curried. We’re unlikely to code this function from scratch every time. Most likely, we’ll either import it from a library, or code it once in our codebase and then import it every time we need it.

This is good, because if we only code the function once, we can test it very thoroughly that one time.

On the other hand, if we code the replace functionality every single time we need it, it would be worse:

  • It would be a lot of redundant and duplicate code.
  • It’s unlikely that we would test every single function as much as we should, since we’ll probably have many of them.
  • At some point, we might code the replace functionality incorrectly, and then we’ll have a bug.

This is a small downside of not using point-free style. Without point-free style, it’s very easy to just code:

const replaceHelloWithGoodbye = str => str.replace('hello', 'goodbye');

And as we just saw, it’s probably better to import and reuse the replace function every single time, rather than recreate the functionality each time.

So this is another reason why the point-free style may be better.

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 be able 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 that you can use in your codebase.

These are similar to the replace function we’ve used in the examples so far.

Some of my favourites include:

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

There are many more.

For the most part, these are just curried, wrapper functions of the corresponding method in JavaScript.

Creating your own point-free functions

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

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

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

Secondarily, just like in imperative programming, you may choose to prioritise a parameter order that makes sense, rather than just follow order of importance.

For example, in the replace function, you may consider that replacee is more important than replacement. Therefore, if ordering purely by order of importance, replacee should come after replacement. But it’s fine. If you feel that, importance aside, it’s more natural for replacee to come first, that’s not a problem, especially if the parameters have roughly similar importance.

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

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

So to summarize, more important parameters should come later. But if parameters are close enough in importance, you can use a more "common sense" order.

If you need additional convincing that the data should come last, examine the replace and replaceHelloWithGoodbye examples 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 and their parameter order, just check out the documentation on functions of a functional programming library like Ramda.

Summary of point-free

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

Less code = less bugs.

Also, point-free style encourages us to reuse existing utility functions, rather than recreate them from scratch each time. This makes code safer, as we can reuse a utility we’ve thoroughly tested.

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 this fake example:

// 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 fake, this kind of pattern is fairly common in real codebases.

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

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

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

const foo1 = chain(doubleArray, calculatePrice, addVAT);
// or
const foo2 = compose(addVAT, calculatePrice, doubleArray);

foo1([1, 2, 3, 4, 5]);
foo2([1, 2, 3, 4, 5]);

All chain does is call the functions from left to right, 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.

compose does exactly the same but in the opposite direction. The function calls go from right to left.

The process of using compose is known as "function composition". chain is exactly the same concept.

I personally consider example 2 to be significantly cleaner than example 1.

Complex function composition

There are many 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.

But you can use function composition with all of them. 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 we can do conditionals, or anything else we 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, for me, example 1 is 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 my codebase.

If you really wanted to apply separation of concerns and DRY to the max, 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 than the others, which might make example 3 look better.

But anyway, in other words, 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 good software, 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, because we should be using immutability wherever possible anyway. This is covered in more detail in the post clean code and programming principles.

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 we don’t have to hunt through the codebase to try and track their values.

But be pragmatic. One reasonable exception to immutability may be within the local scope of functions.

For example:

function sumRange1(n) {
  let result = 0;
  for (let i = 1; i <= n; i++) {
    result += i;
  }
  return result;
}

function sumRange2(n) {
  if (n <= 0) {
    return 0;
  }
  return n + sumRange2(n - 1);
}

sumRange1 does not enforce immutability. The values of i and result change as the function executes.

sumRange2 uses immutable values. The value of n never changes as the function executes.

But from an outside caller’s point of view, both versions are exactly the same (except for some technical issues with recursion, which we’ll ignore).

The important thing is that neither of these functions mutate any values which are outside of their local scope. That would be far more dangerous.

For more information on immutability, checkout 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 wherever possible. That’s a requirement of 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 or the database.

There isn’t really much else you can do. 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.


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 I see some of the motivation behind them, and I hope I can help you do the same.

In this section I want to do two things:

  1. Describe monads, on a very high, conceptual-only, level.
  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.

Overall, I’m going to skip a lot of the details. This will be a high level explanation.

What monads are

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 would be private and not accessible from the outside.)

Additionally, monads are immutable, but this is just a small detail we’ll ignore in this example.

A monad is basically just a container for a value.

The value it contains is never accessible from the outside.

Instead, to do stuff that we normally would with the value, we pass in a function. That function accepts that value as an argument, and does something with it. Then, we replace the old value with the new value inside our monad.

That’s all it really is. Just a container for a value, where we pass in functions to operate on the value.

For example:

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

const myMonad = new MonadLikeClass(500);
myMonad.map(double);

Now myMonad‘s value property is 1000.

JavaScript promise example

A JavaScript promise is another good example, because a promise is basically just the Task monad.

Sure, there are some minor, technical differences. But for the most part it’s just the Task monad with a different name.

So what is a promise?

It’s the same thing as other monads. It’s a container for a value. In addition, it contains some internal code to handle asynchronous programming.

After a promise resolves, it contains a value.

A promise doesn’t return that value to the outside. It’s a container and the value stays in there.

Instead, you give it a function to run.

const someAsyncTask = Promise.resolve('foo');
someAsyncTask.then(console.log); // foo

In this example, someAsyncTask immediately resolves to 'foo'. The promise someAsyncTask holds the value 'foo' inside it.

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

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

That’s basically what monads are.

They’re just containers of values. The values are stuck in there, and we pass functions to them to continue doing stuff with the values. They never return the value. They return a new monad, containing the new value.

Motivation for monads

The main benefits of a monad isn’t from containing a value. That doesn’t really do anything on its own.

The benefit comes from other functionality monads also contain.

Benefits of promises

For example, consider JavaScript promises again.

Take a moment to consider: Do we need promises? Are promises useful?

We definitely don’t need them. Before promises existed in JavaScript, we could do everything we needed. It just took a lot of extra code that promises now handle for us. More proof of this is that we can polyfill promises (recreate promises) using plain JavaScript code. If we can recreate promises, it also means we can do everything they do by just using plain old code.

So, similar to a lot of things in functional programming, they are not needed, they are just helpful.

Promises provide a lot of benefits:

  • They save us from callback hell.
  • They encapsulate a lot of code making sure they can only be triggered once.
  • They provide a good structure for asynchronous programming with events, etc.
  • And more.

One of the benefits, is ensuring that your function is only called once. We can code that ourselves without promises. Here is a possible implementation for an onlyOnce utility:

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)

But of course, promises save us from ever having to write this code.

They contain something which handles the onlyOnce functionality within them.

This, of course, is really helpful.

And they also do far more than that.

So this is an example, where promises (or the Task monad), are very 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.

For a random example, let’s have a function 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.

Even if this example is fake, there are always valid scenarios in the codebase where we need null checks. For now, let’s just roll with this example.

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

If we forget the null check, then under some scenarios our code will crash and we will have a bug.

The problem is that we often forget. At other times, we just don’t know. Maybe we’ve never worked with the module in question so we have no idea we need a null check here and another one there.

Consider: How are we 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 we forget to check for null we just crashed our 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 that you must 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’s how we might use the Maybe monad.

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

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

// 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'

Here is a simplified version of the Maybe monad.

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

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

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

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

So hopefully, this shows you how this monad is very 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:

  • We don’t need monads. They are just very useful.
  • Monads are mostly just containers for values. We can’t get the values out. Instead we pass functions to the monads to work on the values.
  • They also contain additional code which does useful things. For example the Maybe monad always runs a null check on its value before running the provided function.

There are many more things you need to know to work with monads effectively, but I’m not covering them here.

I hope I was able to show you some of the motivation for using them. Next, if you want to explore monads further, I suggest you try a more thorough resource for them to continue your learning.

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 them and get comfortable with them yourself. That way, you can actually use them if you want to. You can also make a more informed decision as to whether they’re worth using or not.


Functional reactive programming

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 their order is important.

It’s okay if that doesn’t make much sense yet.

Let’s start simple, and build up to something we might model as a stream.

Synchronous, single value

Here is something that’s definitely not a stream.

A synchronous operation that we 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, we have a synchronous operation that we 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, we consider an asynchronous operation, which we can simply activate any time an event occurs. In other words, an asynchronous operation we do on a single value at a time.

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);

Still relatively easy and straightforward.

Asynchronous, multiple events

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

To illustrate the point, let’s consider this example:

We provide an input for the user, so they can type a search term. As the user types, we read their search term and use it to submit a network request to get relevant information from an API. Then we display the results to 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 sufficient.

There are more things we need to handle:

  • We don’t want to perform a search until the user stops typing for some time, say, 0.5 seconds. If we don’t do that, we 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, we want to "throttle" the events and only handle them every 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)? We don’t want to perform another network request unless the search term is different.
  • How do we handle race conditions? With the current implementation, it’s not guaranteed that the last network request will be the one shown to the user. Rather, the slowest network request may be shown, the one that completes last, rather than the one 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’s a version of how we might do it:

// Utilities
function throttle(fn, delay) {
  let timeoutId = null;

  return function execute(...args) {
    clearInterval(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;
}

// Our 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 throttledHandleInput = throttle(handleInput, 500);

// Set up the event

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

That is a lot of code. It’s also quite messy.

Alright, so at least the functions throttle, distinct and maybe even onlyLatestPromise can be considered to be utilities. We might be able to import them from a library.

Some of the other functions like getSearchTerm, makeRequest and handleResponse are our 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 we need to carefully and sequentially join them up so that our code works.

As an alternative to this approach, we could have had a god function that does everything inside it. That would save us 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, throttleTime, 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(
  throttleTime(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 our custom implementation where we joined up all 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 us a lot of time.

As with everything else, we don’t need it. However, the difference is significant. If we’re working with things that can be modelled as streams, a functional reactive programming library can save us a lot of time and make our code much cleaner.

What is a stream?

To summarize the point one last time: A stream is a collection data or events that are asynchronous (arrive over time).

It makes sense to model things as streams in our application when our operations on each chunk (individual data or event) also require information about previous chunks.

Common use-cases are when we need to use the operators that RxJS provides (have a look at the RxJS documentation for more details). Operators like:

  • 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 deem it worthwhile, I recommend you continue your learning. You can get more practice with the basics we covered here by learning from a much more thorough resource. There are also many more things we 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 was very thorough and covered 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. However, it covers monads very thoroughly. In fact, the majority of the book teaches 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.