Search code examples
listhaskellcomonad

Theoretically, is this a valid comonad instance for a list?


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?


Solution

  • 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:

    1. coreturn . cojoin = id
    2. fmap coreturn . cojoin = id
    3. 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.