I'm trying to grasp the concept of comonads, and after reading this blog post, I think I have a solid understand of what they do and how they are related to monads. But, I thought I would delve into the subject a little bit and just think about what the comonad instance of the generic list type (you know, [a]
) would look like, and I've come to a piece I don't fully know is correct.
So, given the instance that the blog post used:
class Functor w => Comonad w where
(=>>) :: w a -> (w a -> b) -> w b
coreturn :: w a -> a
cojoin :: w a -> w (w a)
I thought that the instance declaration for [a]
would look something like this (the syntax for [a]
is probably either impossible or wrong, but you get the idea here):
instance Comonad [a] where
coreturn = head
cojoin = Data.List.subsequences --this is what I'm confused about
x =>> f = map f (cojoin x)
Here, we just find all of the subsequences
of the list, but it would be entirely feasible to just use it's powerset
or something. There are several functions on lists of the form (a -> [a])
, and it's sort of ambiguous as to which one is correct.
Does this mean that [a]
cannot be properly instantiated properly as a comonad, or is it simply up to the user to decide what cojoin
will actually do?
As noted in the comments, you cannot have a comonad instance for lists that may be empty since coreturn
has to return something.
Apart from that, your instance also has to satisfy the comonad laws. Expressed in terms of coreturn
and cojoin
, these are:
coreturn . cojoin = id
fmap coreturn . cojoin = id
cojoin . cojoin = fmap cojoin . cojoin
You can easily see that these do not hold for your instance even if we disallow empty lists. However, assuming that coreturn
is head
, we can use these laws to get some clues as to what cojoin
must be.
From (1), we can determine that the first element of the list returned by cojoin
must be the original list, and from (2) we see that combining the first elements of each inner list must also yield the original one. This strongly suggests that we need something like tails
*, and it can be confirmed that this satisfies (3) as well.
* More specifically, we need a version of tails
that does not include the empty list at the end.