Functor and Monad
Tags: functional, typescript, fpts
Published: 2022-11-02 01:52:26

Functors and monads are concepts that are often misunderstood and over-explained at the same time. Many tutorials and blog posts focus on why these concepts are useful instead of what they actually are. This is understandable since it is actually not required to know what these concepts are in order to take advantage of them.

In this post, we first look into what a functor and a monad are. After that, we switch over to what we can achieve by using them.

## Functor

The word **functor** is the name of a specific interface which describes the following property: when we have _something_ of type `a`, and we have a way to take `a` and map it to `b`, we can have _something_ of type `b` <sup>[[1]]</sup>. _Something_ needs to be a **source** of a value of some type. The word "source" is chosen deliberately to represent an abstract thing because it can be pretty much anything. Let's take a look at a couple of examples of a source of type `string`:

```ts
// the values are accessed by iteration
export type ArraySource = Array<string>;

// the value is accessed through property `a`
export interface RecordSource {
  a: string;
}

// the value is accessed through a function call
export interface FunctionSource {
  (): string;
}
```

All of the above are sources of type `string` because there is a way to access the value. Array, Record, and Function are functors because they specify a way to _map_ their enclosed values to a potentially different type. We can also think of functors as [type classes] specifying some kind of a _map_ function. That function does not necessarily have to be called `map` but needs to behave like one. For example, the type `Array` is a functor because it defines a `map` function.

```ts
// source of values `a` (Array<number>)
const numbers = [1, 2, 3];

// mapping of `a` (number) to `b` (string)
const toString = (a: number): string => String(a);

// source of values `b` (Array<string>)
export const result = numbers.map(toString);
```

`Array#map` has the ability to **take** an `a` (of any type) from an instance of Array by iterating over its values, apply a mapping function on each `a`, and produce a new instance of Array holding values of `b` of type that is dictated by the return type of our mapping function.

How would a `Function#map` look like?

```ts
interface Fn<A> {
  (): A;
}

interface FnMap {
  <A, B>(fn: (a: A) => B): (a: Fn<A>) => Fn<B>;
}

const functionMap: FnMap = (fn) => (a) => {
  return () => fn(a());
};

// source of values `a` (Fn<number>)
const makeNumber = () => 42;

// mapping of `a` (number) to `b` (string)
const toString = (a: number): string => String(a);

// source of values `b` (Function<string>)
export const result = functionMap(toString)(makeNumber);
```

`functionMap` has the ability to **take** an `a` (of any type) from an instance of type `Function` by calling it, apply a mapping function on its return value, and produce a new instance of `Function` holding a value of `b` of type that is dictated by the return type of our mapping function.

In both examples, we use the exact same mapping function (`toString`) without having to change anything about it. The same function can then be applied to both functors: `Array` and `Function`. In fact, we can supply that mapping function to any functor's map function exactly because the term functor describes a property, and not a noun. Reusing code without additional modifications is extremely powerful.

## Monad

Just like functor, the word **monad** is also the name of a specific interface. A monad is something that fulfils the following:

- it is a **functor**, i.e. implements a `map` function
- it can be **chained**, i.e. implements a `flatMap` function
- it can be **constructed**, i.e. implements an `of` function
- it abides by the [Monad laws]

### Chainability

The definition of chainability is very similar to functor's mappability. The difference is highlighted in bold letters: when we have something of type `a`, and a way to map `a` onto **the same type of something** of type `b`, we can have something of type `b` <sup>[[1]]</sup>. Same as with functor, _something_ needs to be a **source** of a value of some type. We can also think of monads as [type classes] specifying some kind of a _flatMap_ function. That function does not necessarily have to be called `flatMap` but needs to behave like one. For example, the type `Array`, besides being a functor, is also a monad because it defines a [`flatMap`] function.

```ts
// source of values `a` (Array<string>)
const expressions = ["Functors and monads", "are easy"];

// mapping of `a` (string) to the same type of source (Array) of `b` (Array<string>)
const splitByWords = (a: string): string[] => a.split(" ");

// source of values `b` (Array<string>)
export const words = expressions.flatMap(splitByWords);
// ["Functors", "and", "monads", "are", "easy"]
```

So, how does our example with `flatMap` fit the definition? Let's break it down:

- "when we have something of type `a`": `expressions` - the type of the source is `Array<string>`.
- "and a way to map `a` onto the same type of something of type `b`": `splitByWords` maps `string` onto `Array<string>` (matches the type from above).
- "we can have something of type `b`": `expressions.flatMap(splitByWords)` produces `Array<string>` because `flatMap` knows two things:
  1. how to provide `a` to the mapping function,
  2. how to **flatten** the intermediate results produced by the mapping function.

Evaluating `expressions.map(splitByWords)` would produce a value of type `Array<Array<string>>`. Hence, things that implement a function like `map` but don't implement a function like `flatMap` cannot be called monads.

### Constructability

A monad also needs to implement a function that may take a value and returns an instance of the monad encapsulating that value. Many times these functions will be called `of`, as in `Task.of(42)`, but can also be called anything else, e.g.: `left`, `right`, `some`, `none`, etc.

### Monad laws

The explanation of monad laws is beyond the scope of this series. However, if you're interested, you can read [Monad laws for regular developers] written by Miklós Martin. It provides a nice explanation using both Scala and JavaScript examples.

## Why are functors and monads useful?

In my opinion, they are useful because of their following three innate characteristics:

1. They are **polymorphic**.
2. They abstract away some boilerplate code such as iteration and control flow logic.
3. They are **composable**.

### Polymorphism

Polymorphism just means that an object can take up multiple forms. For example, the type `Array` can be of `string`s, `number`s or any other type.

Functions like `map` and `flatMap` don't care what the type of the value is. That is the responsibility of the mapping function. With `map` and `flatMap`, we can easily go from an `Array<number>` to an `Array<string>`.

```ts
const numbers = [1, 2, 3];
export const strings = numbers.map(String);
// ["1", "2", "3"]
```

This is a great property to have because we don't have to implement `map` and `flatMap` functions for every possible type of Array out there.

### Useful abstractions

Functors and monads abstract away some common boilerplate code such as iteration and control flow logic. By using functors and monads, we repeat less code. Also, we tend to write more **declarative** code instead of imperative.

_Imperative code_ is more prescriptive, i.e. we say **how** a certain thing should be implemented.

_Declarative code_ is more descriptive, i.e. we say **what** a certain thing should do. People who write code are humans, and humans make mistakes. So, the less we have to describe _how_ things should be implemented, the more of the following may apply to our code:

- has fewer bugs,
- runs faster,
- is more secure.

Obviously, this applies only if the functor/monad implementation is well tested, optimized for speed and implemented with security in mind.

#### Iteration and Control flow

The previously shown code examples demonstrate how `map` and `flatMap` both hide the iteration logic. To demonstrate the control flow logic being abstracted away, we'll need to assume something about how Array's `map` function is implemented.

```ts
export const map =
  <A, B>(fn: (a: A) => B) =>
  (x: A[]): B[] => {
    if (x.length === 0) return [];

    // pretend to be the rest of map's implementation
    return x.map(fn);
  };
```

This is a simple and not very useful optimization but it serves its purpose well.

```ts
[]
  .map((x) => x + 1)
  .map((x) => x * 2)
  .map((x) => x % 2);

// []
```

Every time map is called, an empty array is returned early skipping possibly expensive and unnecessary computations. The real value of abstracting the control flow logic will be more apparent when we'll look into the `Either` and `Option` monads in the upcoming chapters.

### Function composition

Because of how functor and monad is defined, it lends itself to function composition very well. Remember how `Array#map` and `Array#flatMap` work? They both return an instance of `Array`. This means, we can keep calling `map` and `flatMap` in succession:

```ts
const splitByWords = (a: string): string[] => a.split(" ");
const len = (a: string): number => a.length;
const double = (a: number): number => a * 2;

const expressions = ["Functors and monads", "are easy"];
export const result = expressions.flatMap(splitByWords).map(len).map(double);
// [ 16, 6, 12, 6, 8 ]
```

Function composition in this example is achieved by method chaining. If we want, we can create our own `map` with a slightly different interface and use `pipe` from the previous chapter:

```ts
import { pipe } from "fp-ts/lib/function.js";

const map =
  <A, B>(f: (_: A) => B) =>
  (a: A[]): B[] =>
    a.map(f);
const len = (a: string): number => a.length;
const double = (a: number): number => a * 2;

export const result = pipe(
  ["Functors", "and", "monads", "are", "easy"],
  map(len),
  map(double)
);
// [ 16, 6, 12, 6, 8 ]
```

The advantage of redefining `map`'s interface is that we can "chain" functions other than `map` and `flatMap` as well. Luckily for us, `fp-ts` already did that for both `map` and `flatMap`, so we could have written our example just as:

```ts
import { array } from "fp-ts";
import { pipe } from "fp-ts/lib/function.js";

const splitByWords = (a: string): string[] => a.split(" ");
const len = (a: string): number => a.length;
const double = (a: number): number => a * 2;

export const result = pipe(
  ["Functors and monads", "are easy"],
  array.chain(splitByWords), // flatMap is called chain in fp-ts
  array.map(len),
  array.map(double)
);
// [ 16, 6, 12, 6, 8 ]
```

## Wrap-up

- Functors and monads can be thought of as interfaces.
- Functor is an interface specifying a function similar to `Array#map`.
- Monad is a functor that also:
  - specifies a function similar to `Array#flatMap`,
  - has a constructor: `Array()`,
  - abides by the [Monad laws].
- Functors and monads are useful because they are:
  - polymorphic,
  - reduce code repetition,
  - compose well.

The next part of this series delves into a very specific type of monad implemented in `fp-ts`: the **Either** monad.

## Extra resources

- [Functors and Monads For People Who Have Read Too Many "Tutorials"]
- [What is a Monad? - Computerphile]
- [The Absolute Best Intro to Monads For Software Engineers]
- [Getting started with fp-ts]
- [mdn: Array#map]
- [mdn: Array#flatMap]
- [All code samples in this post are available on Github]

[1]: http://www.jerf.org/iri/post/2958
[type classes]: https://en.wikipedia.org/wiki/Type_class
[`flatMap`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
[Promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
[Functors and Monads For People Who Have Read Too Many "Tutorials"]: http://www.jerf.org/iri/post/2958
[What is a Monad? - Computerphile]: https://www.youtube.com/watch?v=t1e8gqXLbsU
[The Absolute Best Intro to Monads For Software Engineers]: https://www.youtube.com/watch?v=C2w45qRc3aU
[mdn: Array#map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
[mdn: Array#flatMap]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap
[Monad laws]: https://wiki.haskell.org/Monad_laws
[Monad laws for regular developers]: https://miklos-martin.github.io/learn/fp/2016/03/10/monad-laws-for-regular-developers.html
[Getting started with fp-ts]: https://dev.to/gcanti/series/680
[All code samples in this post are available on Github]: https://github.com/vecerek/blog/tree/main/packages/fp-in-ts/src/02-functor-and-monad