Reputation: 2498
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
?
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
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