Dannyu NDos
Dannyu NDos

Reputation: 2498

Is this "Coapplicative" class a superclass for Comonad?

Recall the Applicative class:

class Functor f => Applicative f where
    pure :: a -> f a
    liftA2 :: (a -> b -> c) -> f a -> f b -> f c
    (<*>) :: f (a -> b) -> f a -> f b

    liftA2 h x y = fmap h x <*> y
    (<*>) = liftA2 id

Though it's not immediately clear how to express this class in terms of (mathematical) category theory, it becomes clear when the following function is defined:

liftZ2 :: Applicative f => (f a, f b) -> f (a, b)
liftZ2 (x, y) = liftA2 (,) x y

Here, (,) should be identifiable as the categorical product. Replacing products by coproducts and reversing all the arrows gives the following class:

class Functor f => Coapplicative f where
    copure :: f a -> a
    coliftZ2 :: f (Either a b) -> Either (f a) (f b)

Some instances:

import Data.Functor.Identity
import qualified Data.List.NonEmpty as NonEmpty
import Data.Either

instance Coapplicative Identity where
    copure (Identity x) = x
    coliftZ2 (Identity (Left x)) = Left (Identity x)
    coliftZ2 (Identity (Right y)) = Right (Identity y)

instance Coapplicative NonEmpty.NonEmpty where
    copure (x NonEmpty.:| _) = x
    coliftZ2 (Left x NonEmpty.:| xs) = Left (x NonEmpty.:| lefts xs)
    coliftZ2 (Right y NonEmpty.:| ys) = Right (y NonEmpty.:| rights ys)

instance Coapplicative ((,) e) where
    copure (_, x) = x
    coliftZ2 (e, Left x) = Left (e, x)
    coliftZ2 (e, Right y) = Right (e, y)

instance Monoid m => Coapplicative ((->) m) where
    copure f = f mempty
    coliftZ2 f = case copure f of
        Left x -> Left (fromLeft x . f)
        Right y -> Right (fromRight y . f)

I have a strong intuition that Coapplicative is a superclass for Comonad, but I don't know how to prove it. Also, is there a Coapplicative instance that is not a Comonad?

Edit

I forgot to mention that liftA2 can be derived from liftZ2:

liftA2 f x y = fmap (uncurry f) (liftZ2 x y)

Also, the instance Coapplicative NonEmpty above is wrong. It must be:

instance Coapplicative NonEmpty.NonEmpty where
    copure (x NonEmpty.:| _) = x
    coliftZ2 (Left x NonEmpty.:| xs) = Left (x NonEmpty.:| fmap (fromLeft x) xs)
    coliftZ2 (Right y NonEmpty.:| ys) = Right (y NonEmpty.:| fmap (fromRight y) ys)

Upvotes: 6

Views: 210

Answers (1)

luqui
luqui

Reputation: 60463

This is not a complete answer, but I can at least make the case that your instance of Coapplicative NonEmpty cannot be derived from Comonad methods alone; that is, if there is some parametric implementation of

coliftZ2 :: (Comonad w) => w (Either a b) -> Either (w a) (w b)

then it does not generate your instance for NonEmpty. This is because the methods of Comonad NonEmpty alone do not afford us any way of changing the length of a list, but your NonEmpty instance of coliftZ2 changes the length. So if NonEmpty is to be a Coapplicative, it must do so in some other way, or else Coapplicative can't be a superclass of Comonad.


As you investigate further, I would say it is worth exploring the comonad

data Two a = Two a a
    deriving (Functor)

instance Comonad Two where
    extract (Two x _) = x
    duplicate (Two x y) = Two (Two x y) (Two y x)

and considering what its coliftZ2 implementation must be. You haven't given any laws for Coapplicative, but if you did, I would put my money on this breaking them because there is no implementation of coliftZ2 which is satisfyingly symmetrical. Both of the following equations are rather forced, but they seem to indicate very different intentions for the operation:

coliftZ2 (Two (Left x) (Left y)) = Left (Two x y)
coliftZ2 (Two (Left x) (Right _)) = Left (Two x x)

In my opinion the question that will shed the most light on this is: What should we expect of the relationship of coliftZ2 to the comonad methods?

Upvotes: 2

Related Questions