While thinking about comonads as spaces and Day convolution, I realized an interesting thing. The free applicative functor generated by a comonad f
is also a comonad.
The free applicative can be defined in a few different ways, but I like to define it like this:
data FreeApplicative f a = Pure a | Free (Day f (FreeApplicative f) a)
Here, Day
is taken from my purescript-day
package. I think this presentation makes it easy to understand the free applicative, since it is just a Day convolution of a finite number of copies of f
. It also makes it simple to define various operations like hoistFreeAp
.
If we refactor this slightly to use a Coproduct
, then a Comonad
instance can even be derived:
newtype FreeApplicative f a = FreeApplicative (Coproduct Identity (Day f (FreeApplicative f)) a)
derive newtype instance functorFreeApplicative :: Functor f => Functor (FreeApplicative f)
derive newtype instance extendFreeApplicative :: Extend f => Extend (FreeApplicative f)
derive newtype instance comonadFreeApplicative :: Comonad f => Comonad (FreeApplicative f)
This works since Identity
is a comonad, and Coproduct
and Day
both preserve comonads.
In the comonads as spaces approach to user interfaces, the free applicative gives a way to build a UI for a finite list of subcomponents, just like Day
gives us a way to compose two subcomponents.
Unfortunately, FreeAp
isn't a Comonad
transformer. The problem is that we would not be able to handle the Pure
case if we tried to lower
a FreeAp w
to a w
.
However, we can construct a ComonadTrans
instance if we restrict ourselves to a Day convolution of a non-zero number of copies of f
. Instead of the free Applicative
, this gives us the free Apply
:
newtype FreeApplicative f a = FreeApplicative (Coproduct Identity (FreeApply f) a)
derive newtype instance functorFreeApplicative :: Functor f => Functor (FreeApplicative f)
derive newtype instance extendFreeApplicative :: Extend f => Extend (FreeApplicative f)
derive newtype instance comonadFreeApplicative :: Comonad f => Comonad (FreeApplicative f)
newtype FreeApply f a = FreeApply (Day f (FreeApplicative f) a)
derive newtype instance functorFreeApply :: Functor f => Functor (FreeApply f)
derive newtype instance extendFreeApply :: Extend f => Extend (FreeApply f)
derive newtype instance comonadFreeApply :: Comonad f => Comonad (FreeApply f)
Now, we can write a ComonadTrans
instance for FreeApply
which keeps the first w
in a non-empty collection of w
s, and extract
s the focus from the rest.
instance comonadTransFreeApply :: ComonadTrans FreeApply where
lower (FreeApply d) = runDay (\f w fa -> map (\x -> f x (extract fa)) w) d