haskellmonadsapplicativecomonad

Applicative is to monad what X is to comonad


Can we solve this equation for X ?

Applicative is to monad what X is to comonad


Solution

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