29

I know what a monad is. I think I have correctly wrapped my mind around what a comonad is. (Or rather, what one is seems simple enough; the tricky part is comprehending what's useful about this...)

My question is: Can something be a monad and a comonad?

I foresee two possible answers:

  • Yes, this is common and widely useful.
  • No, they do such different jobs that there would be no reason to want something to be both.

So, which is it?

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • 12
    Well, `Identity` is a trivial example. – Matvey Aksenov May 14 '13 at 19:59
  • 3
    Both `Reader` and `Writer` can be comonads as well, though what they do is different and which requires a `Monoid` constraint is swapped. – C. A. McCann May 14 '13 at 20:04
  • 1
    Non-empty lists is another example. – hammar May 14 '13 at 20:09
  • 2
    Oi, Haskell greats, this is your time to shine! Come out of the comments and start explaining! I'd love a written up answer or three to this. Comonads are poorly understood generally (and particularly by me), and this sort of comparison question would be enlightening. – AndrewC May 14 '13 at 20:15
  • This question might be better for the Theoretical Computer Science SE, but I'm sure you'll get some knowledgeable people answering here too! – Noldorin May 14 '13 at 20:40
  • 1
    @Noldorin You're forgetting that in Haskell, understanding category theory in a practical way is as important as understanding the link between stacks and recursion in a more procedural language. – AndrewC May 14 '13 at 20:51
  • @AndrewC: Few Haskell programmers that I know properly understand category theory though (as a mathematician would). It's a rather theoretical issue, and far more complicated than recursion and stacks. – Noldorin May 14 '13 at 23:27
  • @Noldorin As you can see, this question has very practical, code-filled answers, which proves the point I was trying (clumsily) to make. This __practical__ approach to category theory is important to haskell programmers, and this question is far from being a theoretical one. – AndrewC May 15 '13 at 06:55
  • @AndrewC: No need to be combative mate; I'm not saying this question should be closed. I was just offering a helpful tip to the questyion asker. I actually agree with you... category theory can be a highly abstract and theoretical tool in mathematics, or it can be a rougher conceptual underpinning of functional programming, or anywhere inbetween. – Noldorin May 15 '13 at 14:08
  • @Noldorin OK sorry, you're right, and were offering help. You weren't to know that MathematicalOrchid is the top asker of all time on the Haskell tag. – AndrewC May 15 '13 at 17:34
  • @AndrewC I'm not _that_ prolific am I?? o_O – MathematicalOrchid May 15 '13 at 20:01
  • 1
    @AndrewC: Hah, no worries mate. Glad this question has found its place here and seemingly gotten some decent answers. – Noldorin May 15 '13 at 21:31
  • 2
    @MathematicalOrchid There's quantity (others have asked more), but there's definitely also high quality. You get on average 18 upvotes per question. Your questions are typically very interesting indeed, and I sometimes just browse them for interesting things to learn. Please keep asking. Data source: there's a [top users](http://stackoverflow.com/tags/haskell/topusers) link when you click on any [haskell](http://stackoverflow.com/questions/tagged/haskell) tag. – AndrewC May 15 '13 at 21:40

5 Answers5

27

The Cofree Comonad yields some data structures that are useful as Monads and Comonads both:

data Cofree f a = a :< f (Cofree f a)

Every Cofree Comonad over an Alternative functor yields a Monad -- see the instance here:

http://hackage.haskell.org/packages/archive/free/3.4.1/doc/html/Control-Comonad-Cofree.html

instance Alternative f => Monad (Cofree f) where
  return x = x :< empty
  (a :< m) >>= k = case k a of
                     b :< n -> b :< (n <|> fmap (>>= k) m)

This gives us, e.g. nonempty lists as Monads and Comonads both (along with nonempty f-branching trees, etc).

Identity is not an alternative, but Cofree Identity yields an infinite stream, and we can in fact give a different monad instance to that stream:

http://hackage.haskell.org/packages/archive/streams/3.1/doc/html/Data-Stream-Infinite.html

data Stream a = a :> Stream a
instance Comonad Stream where
  duplicate = tails
  extend f w = f w :> extend f (tail w)
  extract = head

instance Monad Stream where
  return = repeat
  m >>= f = unfold (\(bs :> bss) -> (head bs, tail <$> bss)) (fmap f m)

(note the functions above are not on lists but instead defined in the streams package).

Similarly the reader arrow is not an alternative, but Cofree ((->) r) yields a Moore machine, and Moore machines also are monads and comonads both:

http://hackage.haskell.org/packages/archive/machines/0.2.3.1/doc/html/Data-Machine-Moore.html

data Moore a b = Moore b (a -> Moore a b)
instance Monad (Moore a) where
  return a = r where r = Moore a (const r)
  Moore a k >>= f = case f a of
    Moore b _ -> Moore b (k >=> f)
  _ >> m = m
instance Comonad (Moore a) where
  extract (Moore b _) = b
  extend f w@(Moore _ g) = Moore (f w) (extend f . g)

So what's the intuition behind all these examples? Well we get the comonadic operations for free. The monadic operations we get are all forms of diagonalization. With alternative we can <|> things together to "smush" the structure, and magic up "empty" things when we run out of structure to smush. This lets us work on finite cases. Lacking alternative we need to have an indefinite amount of structure, so that no matter how many "join" operations (which we can think of as splicing or substitution) that we make, there's always more room to place the spliced elements (like at the Hilbert Hotel: http://www.encyclopediaofmath.org/index.php/Hilbert_infinite_hotel).

Relatedly, every Comonad gives rise to a related Monad (although I consider this more a curiousity):

http://hackage.haskell.org/packages/archive/kan-extensions/3.1.1/doc/html/Control-Monad-Co.html

http://comonad.com/reader/2011/monads-from-comonads/

sclv
  • 38,665
  • 7
  • 99
  • 204
  • 14
    "Every Cofree Comonad over an Alternative Functor yields a Monad" - This is worthy of the same level of fame as "monad is just a monoid of endofunctors"! :-D – MathematicalOrchid May 14 '13 at 20:53
  • The `Alternative f => Monad (Cofree f)` doesn't use any of `Applicative`; it uses neither `pure` nor `<*>`. It only takes advantage of the fact that, for all `a`, `f a` is a monoid. – Cirdec Sep 03 '15 at 00:32
  • True. It should work over any functor with the appropriate induced monoidal structure! – sclv Sep 05 '15 at 22:34
24

Yes. Turning some comments into an answer:

newtype Identity a = Identity {runIdenity :: a} deriving Functor
instance Monad Identity where
  return = Identity
  join = runIdentity
instance CoMonad Identity where
  coreturn = runIdentity
  cojoin = Identity

Reader and Writer are exact duals, as shown by

class CoMonoid m where
  comempty :: (m,a) -> a
  comappend :: m -> (m,m)
--every haskell type is a CoMonoid
--that is because CCCs are boring!

instance Monoid a => Monad ((,) a) where
  return x = (mempty,x)
  join (a,(b,x)) = (a <> b, x)
instance CoMonoid a => CoMonad ((,) a) where
  coreturn = comempty
  cojoin = associate . first comappend

instance CoMonoid a => Monad ((->) a) where
  return = flip (curry comempty)
  join f = uncurry f . comappend
instance Monoid a => CoMonad ((->) a)  where
  coreturn f = f mempty
  cojoin f a b = f (a <> b)
AJF
  • 11,767
  • 2
  • 37
  • 64
Philip JF
  • 28,199
  • 5
  • 70
  • 77
  • I think your implementations are not quite right, but the idea behind them is correct. – C. A. McCann May 14 '13 at 20:38
  • 3
    I really love symmetry like that. Just like in physics, if things are symmetric, it means you're onto something. – Tikhon Jelvis May 14 '13 at 21:04
  • 2
    I just genuinely laughed out loud at i) the symbol names (`flip curry comempty`) and ii) at myself for how little I understood. That deserves an upvote in itself. – Tom W May 14 '13 at 21:06
  • 3
    In case anyone was confused like me, CCC = cartesian closed category (I think) – Clay Thomas Jun 29 '17 at 06:59
17

There are many interesting structures that are both a Monad and a Comonad.

The Identity functor has been pointed out here by several other people, but there are non-trivial examples.

The Writer Monad plays a Reader-like role as a Comonad.

instance Monoid e => Monad ((,) e)
instance Comonad ((,) e)

The Reader Monad plays a Writer-like role as a Comonad.

instance Monad ((->) e)
instance Monoid e => Comonad ((->)e)

Non-empty lists also form both a monad and a comonad and are in fact a special case of a larger construction involving cofree comonads. The Identity case can also be seen as a special case of this.

There are also various Yoneda and Codensity-like constructions based on Kan extensions, that work to transform monads and comonads, although they favor one or the other in terms of operational efficiency.

I also have an adapter that converts an arbitrary comonad into a monad transformer. Sadly the opposite conversion isn't possible in Haskell.

In linear algebra there is a notion of a bialgebra. Ideally if we have something that forms both a Monad and a Comonad and we want to use those operations together without reasoning on a case-by-case basis, one would like to have that return and join are Comonad coalgebras and by extension that extract and duplicate are Monad algebras. If those conditions hold then you can actually reason about code that has both Monad f and Comonad f constraints and mixes the combinators from each without case-by-case reasoning.

Edward Kmett
  • 29,632
  • 7
  • 85
  • 107
5

It depends on what you consider a "monad" to be. If you ask "is it possible for a type to be an instance of both Monad and Comonad at once?" then, yes. Here's a trivial example.

newtype Id a = Id a

instance Monad Identity where
  return       = Id
  (Id a) >>= f = f a

instance Comonad Identity where
  extract (Id a) = a
  extend f ida = Id (f ida)

If you mean it mathematically, then a monad is a triple (X, return, bind) where X is a type and return and bind follow the types and laws you expect. Similarly, a comonad is (X, extend, extract). I've just demonstrated that the Xs might be the same, but since the types of extend and return or extract and bind are different it's not possible for them to be the same functions. So a mathematical monad can never be a comonad.

J. Abrahamson
  • 72,246
  • 9
  • 135
  • 180
1

Expanding on Hammer's suggestion, it seems simple enough to write a function [x] -> [[x]]. For example,

map (\ x -> [x])

would work just fine. So it looks like lists could form a comonad. Ah, but wait. That handles cojoin, but what about coreturn :: [x] -> x? This, presumably, is why only non-empty lists form a comonad.

This gives us a cobind function with the type ([x] -> x) -> [x] -> [x]. Interestingly, Hoogle knows of no such function. And yet we already have concatMap :: (x -> [x]) -> [x] -> [x]. I'm not seeing an immediate use for the cobind function, but I can imagine one existing.

I'm still trying to wrap my mind around comonad and what it might be useful for. The answers so far have given me something to think about...

MathematicalOrchid
  • 61,854
  • 19
  • 123
  • 220
  • 1
    Your `cojoin` doesn't satisfy the comonad laws. In particular, `coreturn . cojoin` should be the identity. The comonad I was talking about has `cojoin = init . tails` and `coreturn = head`. – hammar May 16 '13 at 13:02