22
$\begingroup$

Any monad is also an applicative functor and any applicative functor is a functor. Also, any comonad is a functor. Is there a similar concept between comonads and functors, something like co-applicative functor, and what are its properties?

\begin{array}{c} \end{array} \begin{array}{cc} \mbox{Functors} & & \mbox{Functors} \\ \uparrow & & \uparrow \\ \mbox{Applicative functors} & & ??? \\ \uparrow & & \uparrow \\ \mbox{Monads} & & \mbox{Comonads} \\ \end{array}

Update: I'd be also interested in possible uses of such a concept.

$\endgroup$
4
  • $\begingroup$ Are you sure you weren't looking for Comonads -> ??? -> Cofunctors? $\endgroup$
    – josiah
    Commented Apr 9, 2016 at 2:50
  • 1
    $\begingroup$ @josiah No, as far as I know, comonads are functors, not cofunctors. $\endgroup$
    – Petr
    Commented Apr 9, 2016 at 11:14
  • 1
    $\begingroup$ Isn't divisible that missing piece? $\endgroup$
    – Gus
    Commented Jun 7, 2018 at 21:22
  • $\begingroup$ @Petr Yes, comonads are functors, but cofunctors are functors. $\endgroup$
    – NJay
    Commented Mar 27, 2021 at 9:50

3 Answers 3

21
$\begingroup$

First of all:

Any monad is also an applicative functor and any applicative functor is a functor.

This is true in the context of Haskell, but (reading Applicative as "strong lax monoidal functor") not in general, for the rather trivial reason that you can have "applicative" functors between different monoidal categories, whereas monads (and comonads) are endofunctors.

Further, identifying Applicative with strong lax monoidal functors is slightly misleading, because to justify the name (and the type signature of (<*>)) requires a functor between closed monoidal categories which preserves both the monoidal structure and the internal hom. This could plausibly be called a "lax closed monoidal functor", except that a functor between monoidal closed categories that preserves either property preserves the other in the obvious way. Because Applicative describes only endofunctors on Hask preserving the monoidal structure of (,), its instances gain a lot of properties automatically, including their strength, which can thus be elided.

The apparent connection with Monad is arguably an artifact of the implicit limitations on Applicative causing aspects of their respective monoid structures to coincide, a happy coincidence which unfortunately does not survive dualization.

Just as a comonad on a category $C$ is a monad on $C^{op}$, an oplax monoidal functor $C \rightarrow D$ is a lax monoidal functor $C^{op} \rightarrow D^{op}$. But $ \boldsymbol{Hask}^{op}$ is not monoidal closed, and a co-Applicative that doesn't include function application hardly merits the name. Anyway, the result wouldn't be terribly interesting:

class (Functor f) => CoMonoidal f where
    counit :: f () -> ()
    cozip :: f (a, b) -> (f a, f b)

We could instead imagine a notion of "colax closed functor", which would look much more like Applicative if it existed. Unfortunately, $\boldsymbol{Hask}^{op}$ is not (to the best of my knowledge) a closed category at all: newtype Op b a = Op (a -> b) in $\boldsymbol{Hask}$ corresponds to morphisms $b \rightarrow a$ in $\boldsymbol{Hask}^{op}$, but Op b a doesn't work as an internal hom there--because the arrows are reversed some sort of co-function would be required instead, which we can't define in general for $\boldsymbol{Hask}$.

If we simply pretend that "colax closed functors" existed for $\boldsymbol{Hask}$, and furthermore worked in the way we'd naively hope they would, a co-Applicative based on that would probably look like this:

class (Functor f) => CoApplicative f where
    copure :: f a -> a
    coap :: (f a -> f b) -> f (a -> b)

Adding duplicate :: f a -> f (f a) to copure would produce a comonad (assuming the laws are satisfied), of course. But there's no obvious relationship between coap--whatever it might be--and extend :: (f a -> b) -> f a -> f b. Comparing the types it becomes clear that the dualization is happening in different ways: the comonoidal structures underlying duplicate and cozip have little to do with each other or with coap (which probably doesn't make sense anyway), whereas liftA2 (,) and (<*>) are equivalent and can be derived from join.

Another possible way of dualizing Applicative, which has even less to do with comonads, is to consider contravariant monoidal functors:

class (Contravariant f) => ContraMonoidal f where
    contraunit :: f a
    contrazip :: f a -> f b -> f (Either a b)

But this runs afoul of the same issues as above, namely that $\boldsymbol{Hask}^{op}$ is not a closed category. If it were, we would have some type b <~ a such that we could write functions like contracurry :: (Either c b <~ a) -> (c <~ (b <~ a)) and contraapply :: b -> Either a (a <~ b) and so on that actually worked as expected.

If memory serves me, the obstacles here are not specific to Haskell, but rather arise from $\boldsymbol{Hask}$ being cartesian closed (up to the usual hand-waving, of course), a property it shares with most typed lambda calculi, so you're not likely to get very far with a CoApplicative in most settings.

However, in a monoidal closed category more hospitable to dualization you might have better luck. In particular, I believe both Kleisli (Cont r) and its opposite category are monoidal closed, so that might be a better context to explore these ideas.

$\endgroup$
1
  • 1
    $\begingroup$ Comparing your answer to cstheory.stackexchange.com/a/22302/989, it's surprising that you don't dualize products to sums. Of course, you're right that Hask doesn't have categorical sums; but if you're willing to restrict to the category of total programs (like in Agda), let's pretend it's Set for now, that problem disappears. (I'm not saying Set^op is monoidal closed, but I suspect what I'm saying implies it). $\endgroup$ Commented Feb 21, 2017 at 17:30
11
$\begingroup$

In this post on SO I found an interesting answer - decisive functors. If we replace () by Void, (,) by Either and reverse the arrows, we get:

class Functor f => Decisive f where
    nogood :: f Void -> Void
    orwell :: f (Either s t) -> Either (f s) (f t)

The blog post also gives some laws that decisive functors adhere to.

And, every Comonad is also Decisive:

nogoodW :: (Comonad f) => f Void -> Void
nogoodW = counit

orwellW :: (Comonad f) => f (Either s t) -> Either (f s) (f t)
orwellW story = case counit story of
                      Left s  -> fmap (either id (const s)) story
                      Right t -> fmap (either (const t) id) story 

So decisive functors fit in between functors and comonads, just as applicative functors fit in between functors and monads.

$\endgroup$
6
$\begingroup$

McBride and Patterson (Section 7) show that an applicative functor, also known as an idiom, is a strong lax monoidal functor. You are looking for a strong colax monoidal functor also known as an strong oplax monoidal functor. As mentioned in a comment, an oplax monoidal functor is a lax monoidal functor between the opposite categories, which ends up being a comonoidal version of a lax monoidal functor.

Draw the diagrams, reverse the arrows!

I'd have to spend a bit of time working out the details to see which it is, and to translate it into a functional programming notion.

$\endgroup$
2
  • $\begingroup$ For some reason the standard term seems to be "oplax monoidal functor". The idea is a lax monoidal functor between the opposite categories, which ends up being a comonoidal version of a lax monoidal functor. Using "colax comonoidal" is either redundant or equivalent to "lax monoidal". $\endgroup$ Commented Jan 28, 2013 at 20:13
  • $\begingroup$ I overdid the "co"-ing. I'll fix my answer. $\endgroup$ Commented Jan 28, 2013 at 21:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.