I\'ve been recently teaching myself about the Free
monad from the free package, but I\'ve come across a problem with it. I would like to have different free mo
This is an answer based off of the paper Data types à la carte, except without type classes. I recommend reading that paper.
The trick is that instead of writing interpreters for Bells
and Whistles
, you define interpreters for their single functor steps, BellsF
and WhistlesF
, like this:
playBellsF :: BellsF (IO a) -> IO a
playBellsF (Ring io) = putStrLn "RingRing!" >> io
playBellsF (Chime io) = putStr "Ding-dong!" >> io
playWhistlesF :: WhistelsF (IO a) -> IO a
playWhistlesF (PeaWhistle io) = putStrLn "Preeeet!" >> io
playWhistlesF (SteamWhistle io) = putStrLn "choo-choo!" >> io
If you choose not to combine them, you can just pass them to Control.Monad.Free.iterM
to get back your original play functions:
playBells :: Bells a -> IO a
playBells = iterM playBell
playWhistles :: Whistles a -> IO a
playWhistles = iterM playWhistlesF
... however because they deal with single steps they can be combined more easily. You can define a new combined free monad like this:
data BellsAndWhistlesF a = L (BellsF a) | R (WhistlesF a)
Then turn that into a free monad:
type BellsAndWhistles = Free BellsAndWhistlesF
Then you write an interpreter for a single step of BellsAndWhistlesF
in terms of the two sub-interpreters:
playBellsAndWhistlesF :: BellsAndWhistlesF (IO a) -> IO a
playBellsAndWhistlesF (L bs) = playBellsF bs
playBellsAndWhistlesF (R ws) = playWhistlesF ws
... and then you get the interpreter for the free monad by just passing that to iterM
:
playBellsAndWhistles :: BellsAndWhistles a -> IO a
playBellsAndWhistles = iterM playBellsAndWhistlesF
So the answer to your question is that the trick to combining free monads is to preserve more information by defining intermediate interpreters for individual functor steps ("algebras"). These "algebras" are much more amenable to combination than interpreters for free monads.