We know free monads are useful, and packages like Operational make it easy to define new monads by only caring about the application-specific effects, not the monadic structure itself.
We can easily define “free arrows” analogous to how free monads are defined:
{-# LANGUAGE GADTs #-}
module FreeA
( FreeA, effect
) where
import Prelude hiding ((.), id)
import Control.Category
import Control.Arrow
import Control.Applicative
import Data.Monoid
data FreeA eff a b where
Pure :: (a -> b) -> FreeA eff a b
Effect :: eff a b -> FreeA eff a b
Seq :: FreeA eff a b -> FreeA eff b c -> FreeA eff a c
Par :: FreeA eff a₁ b₁ -> FreeA eff a₂ b₂ -> FreeA eff (a₁, a₂) (b₁, b₂)
effect :: eff a b -> FreeA eff a b
effect = Effect
instance Category (FreeA eff) where
id = Pure id
(.) = flip Seq
instance Arrow (FreeA eff) where
arr = Pure
first f = Par f id
second f = Par id f
(***) = Par
My question is, what would be the most useful generic operations on free arrows? For my particular application, I needed special cases of these two:
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
analyze :: forall f eff a₀ b₀ r. (Applicative f, Monoid r)
=> (forall a b. eff a b -> f r)
-> FreeA eff a₀ b₀ -> f r
analyze visit = go
where
go :: forall a b. FreeA eff a b -> f r
go arr = case arr of
Pure _ -> pure mempty
Seq f₁ f₂ -> mappend <$> go f₁ <*> go f₂
Par f₁ f₂ -> mappend <$> go f₁ <*> go f₂
Effect eff -> visit eff
evalA :: forall eff arr a₀ b₀. (Arrow arr) => (forall a b. eff a b -> arr a b) -> FreeA eff a₀ b₀ -> arr a₀ b₀
evalA exec = go
where
go :: forall a b. FreeA eff a b -> arr a b
go freeA = case freeA of
Pure f -> arr f
Seq f₁ f₂ -> go f₂ . go f₁
Par f₁ f₂ -> go f₁ *** go f₂
Effect eff -> exec eff
but I don’t have any theoretical arguments on why these (and not others) would be the useful ones.
A free functor is left adjoint to a forgetful functor. For the adjunction you need to have the isomorphism (natural in
xandy):In what category should this be? The forgetful functor forgets the
Arrowinstance, so it goes from the category ofArrowinstances to the category of all bifunctors. And the free functor goes the other way, it turns any bifunctor into a freeArrowinstance.The haskell type of arrows in the category of bifunctors is:
It’s the same for arrows in the category of
Arrowinstances, but with addition ofArrowconstraints. Since the forgetful functor only forgets the constraint, we don’t need to represent it in Haskell. This turns the above isomorphism into two functions:leftAdjunctshould also have anArrow yconstraint, but it turns out it is never needed in the implementation. There’s actually a very simple implementation in terms of the more usefulunit:unitis youreffectandrightAdjunctis yourevalA. So you have exactly the functions needed for the adjunction! You’d need to show thatleftAdjunctandrightAdjunctare isomorphic. The easiest way to do that is to prove thatrightAdjunct unit = id, in your caseevalA effect = id, which is straightforward.What about
analyze? That’sevalAspecialized to the constant arrow, with the resultingMonoidconstraint specialized to the applicative monoid. I.e.with
and
Apfrom the reducers package. (Edit: since GHC 8.6 it is also in base inData.Monoid)Edit: I almost forgot, FreeA should be a higher order functor! Edit2: Which, on second thought, can also be implemented with
rightAdjunctandunit.By the way: There’s another way to define free functors, for which I put a package on Hackage recently. It does not support kind
* -> * -> *(Edit: it does now!), but the code can be adapted to free arrows:If you don’t need the introspection your
FreeAoffers, thisFreeAis probably faster.