12

Can we solve this equation for X ?

Applicative is to monad what X is to comonad

nicolas
  • 9,549
  • 3
  • 39
  • 83
  • 3
    `ComonadApply is to Comonad like Applicative is to Monad.` http://hackage.haskell.org/package/comonad-4.2.7.2/docs/Control-Comonad.html#t:ComonadApply It doesn't seem to be very different from Applicative... – danidiaz Jan 17 '16 at 10:18
  • 1
    sounds like an answer ! – nicolas Jan 17 '16 at 10:26
  • 3
    @danidiaz I see that a `ComonadApply` must be a `Comonad` -- unlike `Applicative`s which do not have to be `Monad`s. This looks as a main difference to me. – chi Jan 17 '16 at 11:36
  • 4
    @chi: `ComonadApply` looks like `Apply` (`Applicative` without `pure`). It also looks like it can be implemented without any `Comonad` specific terms. The main difference to `Applicative` is the documentation of the `extract`/`duplicate` + `<*>` laws (and of course `pure`; see PureScript's typeclass hierarchy for an example of `Apply f => Applicative f`). – Zeta Jan 17 '16 at 12:20
  • 1
    @chi: So, the superclass implication arrow `Applicative a => Monad a` *also* is reversed? ;-P – yatima2975 Jan 18 '16 at 11:28
  • @yatima2975 Well, adding "co-" shouldn't reverse _all_ arrows... ;) – chi Jan 18 '16 at 12:53
  • adding co- should reverse all arrows in the category that the typeclass represents. Although that only really helps when there's an identity arrow - without the identity arrow we don't have a category so then it's harder to think about. – codeshot May 28 '19 at 15:53

2 Answers2

9

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.

Zeta
  • 103,620
  • 13
  • 194
  • 236
  • No, not Extract... [Extractive](https://www.merriam-webster.com/dictionary/extractive)! – Matias Pequeno Nov 24 '18 at 18:57
  • ComonadApply w isn't (Apply w, Comonad w). ComonadApply requires zippiness (not growing a structure, rather than Apply's act of growing a structure). For example, the ComonadApply instance for [a] is different to the Apply instance for [a] - it's closer to that of ZipList. – codeshot May 28 '19 at 15:56
  • Of course I mean to use NonEmpty as an example rather than [] – codeshot May 29 '19 at 13:57
0

To me it seems that Apply class should not be a part of the picture at all.

For example the definition of apply in @Zeta's answer does not seem to be well-behaved. In particular, it always discards the context of the first argument and only uses the context of the second argument.

Intuitively, it seems that comonad is about "splitting" the context instead of combining, and so "co-applicative" should be the same.

This question seems to have better answers: Is there a concept of something like co-applicative functors sitting between comonads and functors?.

Greenonline
  • 1,330
  • 8
  • 23
  • 31