Over many previous posts, we have investigated various aspects of monads. To discuss monad theory properly, it is often useful to consider their interaction with the dual notion, comonads. Laying the foundations for such discussions is the aim of the current post. Although comonads can be seen as a special case of monads, that doesn’t really capture how they feel in practice. Our objectives for the current post are:
- To introduce several examples of comonads in the familiar setting of sets and functions.
- To describe some different ways in which comonad are “monads, but upside down”.
Although comonads are formally dual to monads, and therefore “just the same thing”, they are often seen as rather mysterious. A quick experiment on your favourite search engine will find a lot less discussion of comonads than monads for example. One might be tempted to speculate as to why that is, historical mathematical bias, something fundamental about the nature of mathematics,..? We shall avoid such philosophical musing, and concentrating on trying to build up some familiarity.
A comonad on a category consists of:
- An endofunctor .
- A counit natural transformation .
- A comultiplication natural transformation .
This data must satisfy the following counitality and coassociativity equations:
The terms counit and comultiplication drop heavy hints that comonads are a dual notion to monads. We will return to this point shortly. First we plough on, with lots of examples.
Example: Lists are a standard example for monads. For comonads, the non-empty lists provide a similarly canonical example. Let be the endofunctor that sends a set to its collection of non-empty lists. This functor is a comonad, with counit the function that returns the tail of the list, and comultiplication mapping a list to its list of prefixes, for example:
Unlike the list monad, we can bound the length of our lists to form another comonad of non-empty lists of length at most elements.
As with monads, it’s worth considering some instructive degenerate examples. The first one hopefully wont be a big shock.
Example: For any category , the identity functor is a comonad, with the counit and comultiplication both being identity morphisms.
The previous example gives a trivial answer to a natural question. It is possible for an endofunctor to carry the structure of both a monad and a comonad. In fact, so does the endofunctor , which is perhaps a bit more interesting.
The next example is the comonadic analog of the inconsistent monads, in that it provides a healthy source of counterexamples.
Example: For any category with an initial object, the constant functor mapping to the initial object is a comonad. The counit and comultiplication are the unique morphisms:
We might think of this as the overspecified comonad.
Example: For a fixed set , the endofunctor is a comonad, with counit:
Together these form a comonad, which we shall refer to as the reader comonad. Despite the name, this comonad is dual to the exception monad. Computationally, we can think of a map as a function from to that can depend on some additional data it reads from the environment .
Recall that for an adjunction , the composition is a monad. Dually, the composite is a comonad. The following is a standard example of this phenomenon.
Example: For a fixed object , in a Cartesian closed category we have adjunction:
Therefore, the functor:
is a comonad. Computationally, if we think of as a collection of states, morphisms:
bijectively correspond to morphisms:
which transforms state dependent values of type to state dependent elements of type . This comonad is often referred to as the costate comonad.
Example: Given a monoid , the functor is a comonad, with counit:
This is sometimes referred to as the exponent comonad.
A special case of this construction is an important example in itself. Consider the exponent comonad on the monoid of additive natural numbers . We can think of an element of as an infinite lists, or stream, of values from :
The counit returns the first element of the stream, and the comultiplication gives the stream of tails, as follows:
This construction is referred to as the stream comonad.
We now move onto some strategies to systematically construct simple comonads.
Example: Given a non-empty list, we can mark a focus position in it, written as follows:
There is then a natural counit operation, which returns the focal element:
There is also a comultiplication operation, which acts as follows on our example list:
The resulting comonad is the zipper comonad for non-empty lists.
There was nothing special about non-empty lists in the previous example.
Example: Given a pair of values, we can mark a focus position in it, for example:
As in the previous example, there is an obvious counit returning the focus:
There is also a comultiplication, analogously to the previous example, with action:
The resulting comonad is the zipper comonad for pairs.
This pattern of collections of data with a focus can be repeated in great generality to yield many further examples of comonads.
Another example of a similar kind is the following:
Example: Again, we consider the endofunctor . We define our counit to return the tail element of lists. We do something slightly more interesting with the comultiplication, which cycles through the list, as in the following example:
This forms what is know as the circular non-empty list comonad.
The previous example answers another natural question. It is possible for a given endofunctor to carry two different comonad structures. In fact, even if we fix the counit, this remains so.
The zipper comonads, the non-empty list comonad, the circular non-empty list comonad, and several other examples we’ve seen, are all built up in a similar manner:
- There are certain shapes of collections of data types, such as pairs, lists of length 1,2,3, and so on.
- Each shape has a collection of positions in which data is stored. For example, the first and second elements of a pair.
- There is a focus position. In the zippers this was given explicitly, in the non-empty list examples, it was the tail element.
- There is rather general notion of “sub-shape” at a given position.
The first two aspects allow us to describe an endofunctor, mapping a set to the collection values of each shape of a given datatypes, with data at each position taken from . The focus element allow us to build a counit, and the notion of sub-shape is key to describing a comultiplication. Of course, we are only giving imprecise intuitions here, to make all this mathematically precise, we need to introduce more technical machinery. Specifically, we need the framework of (directed) containers, which we may return to in a later post.
It’s all just monads upside-down!
We now return to the question of how monads are related to comonads, which we shall look at from a few different perspectives.
Given a fixed category , we can consider the monads on the opposite category . These consist of:
- An endofunctor on , which is the same thing as an endofunctor on . The presence of the “op” doesn’t really make a great deal of different here, unlike for the natural transformations.
- A unit natural transformation . Due to the reversal of the direction of morphisms in the opposite category, this is the same thing as a natural transformation .
- A multiplication natural transformation . This is the same thing as a natural transformation .
The monad axioms satisfied by and are the same thing as requiring the comonad equations for the corresponding and . That is:
A comonad on a category is the same thing as a monad on the opposite category .
This is all very nice, but in previous posts, we generalised monads to 2-categories and bicategories. The previous explanation does really help us understand comonads in terms of monads in that setting, as it is very specifically tied to monads on categories. We need to look at things in a slightly different way.
Given a 2-category or bicategory , there are a few different ways we can produce new bicategories, by flipping the directions of different cells.
- We can reverse the direction of the 1-cells, yielding a bicategory .
- We can reverse the direction of the 2-cells, yielding a bicategory .
- We can flip the directions of both the 1 and 2 cells, yielding a bicategory .
The operation we are interested in is flipping the direction of 2-cells. It is fairly routine to check that:
A comonad on 0-cell in bicategory is the same thing as a monad on in .
Finally, we note that for a fixed 0-cell in , the category is a monoidal category, with the monoidal structure given by composition and identities. A monad on is the same thing is a monoid in . Dually:
A comonad on a 0-cell in bicategory is the same thing as a comonoid in the monoidal category .
If you’ve not encountered comonoids before, you could equally take this as their definition via comonads.
These more esoteric definitions are useful for performing formal calculations at a high level of abstraction, but concrete examples are often important to develop satisfactory intuitions for applications.
We introduced comonads at a fairly rapid pace, building on our previous experience with monads. There is a lot more we could say about comonads, much of which would dualise the situation with monads. For example:
- There are corresponding Eilenberg-Moore and Kleisli constructions, with a relation to adjunctions. All of this works out in an entirely dual manner to that for monads.
- There is a notion of cofree comonad – again formally dual to that of free monad.
In each case it is worthwhile to examine concrete examples and constructions in familiar categories. There are also interesting questions about the relationships between monads and comonads, and the interaction between them. We will return to such topics in future posts.
Further reading: A good source of information about comonads from a mathematical or computer science perspective is the work of Tarmo Uustalu and Varmo Vene. I would suggest the paper “Comonadic Notions of Computation” as a good starting point.