Applicative is to monad what X is to comonad
After giving it some thought, I think this is actually a backward question. One might think that ComonadApply
is to Comonad
what Applicative
is to Monad
, but that is not the case. But to see this, let us use PureScript's typeclass hierarchy:
class Functor f where
fmap :: (a -> b) -> f a -> f b
class Functor f => Apply f where
apply :: f (a -> b) -> f a -> f b -- (<*>)
class Apply f => Applicative f where
pure :: a -> f a
class Applicative m => Monad m where
bind :: m a -> (a -> m b) -> m b -- (>>=)
-- join :: m (m a) -> m a
-- join = flip bind id
As you can see, ComonadApply
is merely (Apply w, Comonad w) => w
. However, Applicative
's ability to inject values into the functor with pure
is the real difference.
The definition of a Comonad
as the categorical dual consists of return
's dual extract
and bind
's dual extend
(or the alternative definiton via duplicate
as join
's dual):
class Functor w => Comonad w where
extract :: w a -> a
extend :: (w a -> b) -> w a -> w b
-- extend f = fmap f . duplicate k
-- duplicate :: w a -> w (w a)
-- duplicate = extend id
So if we look at the step from Applicative
to Monad
, the logical step between would be a typeclass with pure
's dual:
class Apply w => Extract w where
extract :: w a -> a
class Extract w => Comonad w where
extend :: (w a -> b) -> w a -> w b
Note that we cannot define extract
in terms of extend
or duplicate
, and neither can we define pure
/return
in terms of bind
or join
, so this seems like the "logical" step. apply
is mostly irrelevant here; it can be defined for either Extract
or Monad
, as long as their laws hold:
applyC f = fmap $ extract f -- Comonad variant; needs only Extract actually (*)
applyM f = bind f . flip fmap -- Monad variant; we need join or bind
So Extract
(getting values out) is to Comonad
what Applicative
(getting values in) is to Monad
. Apply
is more or less a happy little accident along the way. It would be interesting whether there are types in Hask that have Extract
, but not Comonad
(or Extend
but not Comonad
, see below), but I guess those are rather rare.
Note that Extract
doesn't exist—yet. But neither did Applicative
in the 2010 report. Also, any type that is both an instance of Extract
and Applicative
automatically is both a Monad
and a Comonad
, since you can define bind
and extend
in terms of extract
and pure
:
bindC :: Extract w => w a -> (a -> w b) -> w b
bindC k f = f $ extract k
extendM :: Applicative w => (w a -> b) -> w a -> w b
extendM f k = pure $ f k
* Being able to define apply
in terms of extract
is a sign that class Extend w => Comonad w
could be more feasible, but one could have split Monad
into class (Applicative f, Bind f) => Monad f
and therefore Comonad
into (Extend w, Extract w) => Comonad w
, so it's more or less splitting hair.