What is a Monad?
Tags: computerscience, programming, categorytheory, mathematics
Published: 2024-04-12 08:17:59

**_A monad is a monoid in the category of Endofunctors._** Okay, post completed, need I say more?... Fine, okay then.

A monad is a very powerful concept allowing for data flow, transformation and control in a managed form. You can think of it as an isolated, self-contained, step within a computation stream. The key component to support this is the bind function:

`(>>=) :: Monad m => m a -> (a -> m b) -> m b`

You might call it `map` or `pipe` although both are slightly incorrect but thats just semantics.

A monad is defined as follows:

> Monad, (from Greek monas “unit”), an elementary individual substance that reflects the order of the world and from which material properties are derived. The term was first used by the Pythagoreans as the name of the beginning number of a series, from which all following numbers derived.
>
> Source: [Britannica](https://www.britannica.com/topic/monad)

## Okay, so what about monoids vs monads?

- A monad is focused on sequencing computations and handling side effects.
- A monoid is focused on combining values, typically with an associative operation `mappend`, also known as `(<>)`, and an identity initialiser `mempty` to create an empty instance.

## What about an Endofunctor?

An `Endofunctor` is a specific type of `Functor` where the source and target categories are the same. In other words, it is a functor from a category to itself.

The core of all Functors is the `fmap` operation:

`fmap :: Functor f => (a -> b) -> f a -> f b`

So where a functor might `fmap` a `Functor String` to a `Functor Int`, an endofunctor would only ever `fmap` a `Functor Something` to `Functor Something`.

Now you know all the components of a Monad. It's incredibly powerful and you actually use them all the time without realising it. There are many common Monads like `Maybe`, `Reader`, `Writer`, `Continuation`, etc.

## An example - The `Maybe` monad

The `Maybe` monad is a monadic data-structure which represents the possibility of a value existing. This is useful in cases where you have computations that may return zero or one value. 

For example, if I have a function `stringToInt(input: string): number` then this would be wrongly typed since we cannot guarantee that the given string _is_ a valid number and defaulting a bad string to `0` or any other number is disingenuous.

Instead we could utilise the type signature `stringToInt(input: string): Maybe<number>` which is far more correct and allows us to contain the possible presence of that value cleanly for future computations to handle.

In typescript I could write the `Maybe` monad as:

```ts
// Maybe Monad
class Maybe<T> {
  private value: T  | null;

  constructor(value: T | null = null) {
    this.value = value;
  }

  // Functor: map
  map<U>(f: (value: T) => U): Maybe<U> {
    return this.value !== null ? new Maybe<U>(f(this.value)) : new Maybe<U>();
  }

  // Monad: flatMap (bind)
  flatMap<U>(f: (value: T) => Maybe<U>): Maybe<U> {
    return this.value !== null ? f(this.value) : new Maybe<U>();
  }
  
  // Example non-required methods you may wish to add
  isJust(): boolean {
    return this.value !== null;
  }

  isNothing(): boolean {
    return this.value === null;
  }

  withDefault(fallback: T): T {
    return this.value !== null ? this.value : fallback;
  }

  // Other more advanced methods you may wish to add
  map2<U, R>(f: (ours: T, theirs: U) => Maybe<R>, other: Maybe<U>): Maybe<R> {
    if(this.value !== null && other.value !== null) {
        return f(this.value, other.value);
    }

    return new Maybe<R>();
  }
}

// Example Usage
const maybeValue: Maybe<number> = new Maybe(5);

// Endofunctor example: mapping over the Maybe value
const incrementedMaybe: Maybe<number> = maybeValue.map(value => value + 1);
console.log(incrementedMaybe); // Output: Maybe { value: 6 }

// Monad example: binding Maybe with another Maybe
const doubledMaybe: Maybe<number> = maybeValue.flatMap(value => new Maybe(value * 2));
console.log(doubledMaybe); // Output: Maybe { value: 10 }

// Monad example: is empty
const empty = new Maybe<string>();
console.log(empty.isNothing()); // Output: true
console.log(empty.isJust()); // Output: false
console.log(empty.withDefault("test")); // Output: "test"

// Monad example: has value
const withValue = new Maybe<string>("abc123");
console.log(withValue.isNothing()); // Output: false
console.log(withValue.isJust()); // Output: true
console.log(withValue.withDefault("test")); // Output: "abc123"

// Combinatory example: Mapping two monadic values
const first = new Maybe<string>("abc");
const second = new Maybe<number>(123);
const third = first.map2(
    (ours: string, theirs: number) => new Maybe<string>(ours + theirs.toString()),
    second
);
console.log(third); // Output: Maybe { value: "abc123" }
```

## Conclusions

Now you should understand Monads. They simply wrap values, can map between values and bind values in a computational pipeline friendly manner: When you think of a Monad, think of type transformations running over time.

I hope that you found some value in todays post and if you have any questions, comments or suggestions, feel free to leave those in the comments area below the post!