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.
After giving it some thought, I think this is actually a backward question. One might think that
ComonadApply
is toComonad
whatApplicative
is toMonad
, but that is not the case. But to see this, let us use PureScript's typeclass hierarchy:As you can see,
ComonadApply
is merely(Apply w, Comonad w) => w
. However,Applicative
's ability to inject values into the functor withpure
is the real difference.The definition of a
Comonad
as the categorical dual consists ofreturn
's dualextract
andbind
's dualextend
(or the alternative definiton viaduplicate
asjoin
's dual):So if we look at the step from
Applicative
toMonad
, the logical step between would be a typeclass withpure
's dual:Note that we cannot define
extract
in terms ofextend
orduplicate
, and neither can we definepure
/return
in terms ofbind
orjoin
, so this seems like the "logical" step.apply
is mostly irrelevant here; it can be defined for eitherExtract
orMonad
, as long as their laws hold:So
Extract
(getting values out) is toComonad
whatApplicative
(getting values in) is toMonad
.Apply
is more or less a happy little accident along the way. It would be interesting whether there are types in Hask that haveExtract
, but notComonad
(orExtend
but notComonad
, see below), but I guess those are rather rare.Note that
Extract
doesn't exist—yet. But neither didApplicative
in the 2010 report. Also, any type that is both an instance ofExtract
andApplicative
automatically is both aMonad
and aComonad
, since you can definebind
andextend
in terms ofextract
andpure
:* Being able to define
apply
in terms ofextract
is a sign thatclass Extend w => Comonad w
could be more feasible, but one could have splitMonad
intoclass (Applicative f, Bind f) => Monad f
and thereforeComonad
into(Extend w, Extract w) => Comonad w
, so it's more or less splitting hair.