Skip to content

Instantly share code, notes, and snippets.

@paf31
Last active September 17, 2019 21:08
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save paf31/0bb290665b5890e989afca9fe88368d8 to your computer and use it in GitHub Desktop.
Save paf31/0bb290665b5890e989afca9fe88368d8 to your computer and use it in GitHub Desktop.
FreeAp f is a Comonad

FreeAp f is a Comonad

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 ws, and extracts 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment