## DEV Community

Mike Solomon

Posted on

I'm writing this short article based on two observations:

1. The internet is filled with many "what is a monad" articles but comparatively few "what is a comonad" articles.
2. These "what is a monad" articles are often written for frontend developers.

I'd like to set the record straight in this article by claiming, in the most hand-wavey and unscientific of ways, that frontend = comonad, backend = monad. So, if you are frontend engineer, learning about comonads is IMO the way to go.

This observation is not at all new. Phil Freeman's article The Future is Comonadic makes the point that working with UIs is tantamount to working with a comonad. So I'm writing this article mostly to amalgamate different strands of thought, clear up confusion, and put a name on certain industry trends.

A monad m providing a context for type a is equipped with two operations:

pure :: a -> m a
join :: m (m a) -> m a

A comonad w providing a context for type a is equipped with two operations:

extract :: w a -> a
duplicate :: w a -> w (w a)

The only difference between the two is what's on the left and right of the arrow.

Let's take a quick gander at a monad and a comonad in the wild.

Maybe is a monad that provides a context of existing or not existing to a value of type a.

data Maybe a = Just a | Nothing

pure for Maybe is defined like this:

pure :: a -> Maybe a
pure = Just

We could have defined it like this:

pure :: a -> Maybe a
pure _ = Nothing

But that wouldn't be very nice, as it would destroy all the input values. While perfectly legal, the community has rallied around the first pure with Just because it is closer to our intuition about how pure for Maybe should behave.

join is defined like this:

join :: Maybe (Maybe a) -> Maybe a
join (Just x) = x
join Nothing = Nothing

As maybes pile up, join gives us a way to squish them back down to a single level of Maybe-ness.

Stream is a comonad that provides an infinite list of values.

data Stream a = Stream a (Stream a)

In that context, extract gets the head of the stream

extract :: Stream a -> a

duplicate gets the tail of the stream.

duplicate :: Stream a -> Stream (Stream a)
duplicate (Stream head rest) = Stream rest

You can then call extract on the result of duplicate to get a Stream and get its head with extract.

# Backend vs frontend

## Backend

In backend development, there are two main tasks:

1. Reigning in the chaos (API calls, flaky hardware, working with the filesystem, GPUs, blech...)
2. Inserting our business logic, the only thing we "know", into this madness to produce value for someone somewhere.

These correspond to:

1. join : Reigning in the chaos.
2. pure : Bringing our business logic to the party.

## Frontend

In frontend development, there are two main tasks:

1. Managing and anticipating possible future scenarios so that when a user does thing x or y it "just works".
2. Rendering a UI for a user.

These correspond to:

1. duplicate : Projecting into the future (the tail of our stream).
2. extract : Rendering a UI.

# Community trends

Massive community trends can be boiled down to the comonad vs monad divide.

## Backend: Kubernetes is a monad

Kubernetes is a monad. You call pure on bits of your stack to get it into a pod, and you call join on pods so that you never have kubernetes-in-kubernetes: there's always just one level.

## Frontend: Nextjs is a comonad

Nextjs is a comonad. You call extract to take JavaScript and make it a static webpage via its SSR, and React hooks are essentially duplicate: they project a value into the future by returning a callback.