In functional programming, **monads** are one of the most pattern for structuring code. But if you've just started exploring the world of **functors** and **applicatives**, understanding how monads fit into the picture might feel daunting.

In this post, we'll walk through how to implement a **monad** in JavaScript, using a simple example. We'll also explain how monads differ from **functors** and **applicatives**, and how applicatives use the `ap`

method to apply wrapped functions to wrapped values.

By the end, you'll have a functional, composable monad that can elegantly handle chained operations and errors.

### What Are Functors, Applicatives, and Monads?

Before diving into the monad implementation, let's quickly recap these three key concepts in functional programming:

**Functor:**A functor is a structure that can be mapped over. In JavaScript, arrays are functors because you can apply a function to each element using`map()`

. Functors follow the rule:`F(a) => F(b)`

.**Applicative:**Applicatives take the concept of functors further. They allow you to apply**wrapped functions**to**wrapped values**. Applicatives have an`ap`

method that lets you do this:`F(f) => F(a) => F(b)`

.**Monad:**Monads extend functors and applicatives with the ability to**flatMap**(also known as`bind`

). This allows you to chain operations that return monads themselves, without ending up with nested structures like`Some(Some(value))`

. Monads follow the rule:`F(a) => (a => F(b)) => F(b)`

.

### Implementing the Functor: `Maybe`

Let's start with a simple **functor** that we’ll later extend into an applicative and monad. This functor, called `Maybe`

, can have two possible states: **Some** (representing a value) or **None** (representing the absence of a value).

```
const Maybe = () => ({
map: (f) => new Error("Must implement the method map!"),
pattern: (pattern) => new Error("Must implement the method pattern!"),
ap: (other) => new Error("Must implement the method ap!"),
flatMap: (f) => new Error("Must implement the method flatMap!")
});
```

This `Maybe`

functor needs to be fleshed out with concrete implementations for **Some** and **None**.

### Adding the `Some`

and `None`

Functors

We'll define `Some`

as a functor that wraps a value and applies functions to it, and `None`

as an empty functor that ignores any function.

```
const Some = (value) => {
const state = { value };
const map = (f) => Some(f(state.value));
const patternMatching = (pattern) => pattern.some(state.value);
return { ...state, map, pattern: patternMatching };
};
const None = () => ({
map: (f) => None(),
pattern: (pattern) => pattern.none()
});
```

`Some`

has a`map`

function that applies the provided function`f`

to the value inside`Some`

.`None`

ignores any`map`

calls since there's no value to work with.

### Adding Applicatives: Implementing `ap`

Applicatives allow us to apply functions wrapped in the functor to values also wrapped in the functor. This is where the `ap`

method comes in.

- In
`Some`

,`ap`

takes another functor (which contains a function) and applies it to the wrapped value. - In
`None`

,`ap`

always returns`None`

because there’s no value to apply the function to.

```
const Some = (value) => {
const state = { value };
const map = (f) => Some(f(state.value));
const patternMatching = (pattern) => pattern.some(state.value);
// Applicative 'ap' method
const ap = (other) =>
other.pattern({
some: (f) => Some(f(state.value)),
none: () => None()
});
return { ...state, map, pattern: patternMatching, ap };
};
const None = () => ({
map: (f) => None(),
pattern: (pattern) => pattern.none(),
ap: (other) => None() // Always return None
});
```

With `ap`

, we can apply a function wrapped in a `Some`

to a value wrapped in another `Some`

.

### Moving from Applicative to Monad: Adding `flatMap`

The key feature of a monad is the ** flatMap** (or

`bind`

) method, which allows you to chain operations that return monads themselves. We'll now implement `flatMap`

for both `Some`

and `None`

.`flatMap`

in`Some`

: Applies a function that returns another monad.`flatMap`

in`None`

: Always returns`None`

.

```
const Some = (value) => {
const state = { value };
const map = (f) => Some(f(state.value));
const patternMatching = (pattern) => pattern.some(state.value);
const ap = (other) =>
other.pattern({
some: (f) => Some(f(state.value)),
none: () => None()
});
// Monad 'flatMap' method
const flatMap = (f) => f(state.value); // Apply function that returns a monad
return { ...state, map, pattern: patternMatching, ap, flatMap };
};
const None = () => ({
map: (f) => None(),
pattern: (pattern) => pattern.none(),
ap: (other) => None(),
// Monad 'flatMap' method always returns None
flatMap: (f) => None()
});
```

### Test Example: Using `map`

, `ap`

, and `flatMap`

Let’s test this monad by applying transformations using `map`

, applying functions with `ap`

, and chaining operations using `flatMap`

.

```
const some5 = Some(5);
const someFn = Some((x) => x * 2);
const none = None();
// Using 'map' to transform the value
console.log(some5.map((x) => x + 1).map((x) => x * 2)); // { value: 12 }
// Using 'ap' to apply a function wrapped in a functor
console.log(some5.ap(someFn)); // { value: 10 }
console.log(none.ap(someFn)); // None
console.log(someFn.ap(some5)); // { value: 10 }
console.log(someFn.ap(none)); // None
// Using 'flatMap' to chain monadic operations
console.log(some5.flatMap((x) => Some(x + 10))); // { value: 15 }
console.log(some5.flatMap((x) => Some(x + 10)).flatMap((x) => Some(x * 2))); // { value: 30 }
console.log(none.flatMap((x) => Some(x + 10))); // None
```

### Real-World Example: Safe Data Fetching

Monads can be incredibly useful when dealing with operations that may fail. For example, when fetching data from an API, you can use `Maybe`

to handle success or failure without writing imperative if/else blocks.

```
const fetchData = (url) => {
const data = fetch(url).then((res) => res.ok ? Some(res.json()) : None());
return data;
};
fetchData('https://api.example.com/data')
.flatMap((data) => Some(data.user))
.flatMap((user) => Some(user.name))
.pattern({
some: (name) => console.log(`User name: ${name}`),
none: () => console.log('User not found.')
});
```

Here, `flatMap`

allows us to keep fetching parts of the data while handling potential failures, like missing or malformed data.

### Conclusion

Monads in JavaScript provide an elegant way to chain operations while handling errors or missing values gracefully. By implementing both the ** ap** (for applicative) and

**(for monad) methods, you unlock the full power of these functional tools, enabling complex workflows to remain predictable and functional.**

`flatMap`

While this post focuses on `Maybe`

, the concepts you’ve learned can be applied to other monads like `Promise`

, `Either`

, or even your custom monads.

Happy coding!