I like the idea of using Free monads to “purify” code and I’ve used it in some simple scenarios. However, I can’t seem to figure out how to write an interpreter for a Free monad transformer. In particular a stack of free monads.
Here’s a simple example of what I have in mind. Say I’m purifying access to two databases. I can write
{-# LANGUAGE DeriveFunctor #-}
module Question where
import Control.Monad.Trans.Free
import Control.Applicative ( (<$>) )
import Control.Monad.Trans.Class ( lift )
data DB1reqF x = FetchName String ( String -> x ) deriving (Functor)
data DB2reqF x = FetchInt String ( Int -> x ) deriving (Functor)
type DB1req = Free DB1reqF
type DB2req = Free DB2reqF
type DB2reqT = FreeT DB2reqF
fetchName :: String -> DB1req String
fetchName s = liftF $ FetchName s id
fetchInt :: String -> DB2req Int
fetchInt s = liftF $ FetchInt s id
runDB1reqF :: DB1reqF ( IO a ) -> IO a
runDB1reqF ( FetchName s n ) = putStrLn ( "Get from DB1: " ++ s ) >> getLine >>= n
runDB2reqF :: DB2reqF ( IO a ) -> IO a
runDB2reqF ( FetchInt s n ) = putStrLn ( "Get from DB2: " ++ s ) >> read <$> getLine >>= n
runDB1 :: DB1req a -> IO a
runDB1 = iterM runDB1reqF
runDB2 :: DB2req a -> IO a
runDB2 = iterM runDB2reqF
which works fine for using DB1 and DB2 separately. But if I want to use them together, I would think to stack a transformer with the other like
type CompoundProg = DB2reqT DB1req
fetchIntT :: (Monad m) => String -> DB2reqT m Int
fetchIntT s = liftF $ FetchInt s id
complexProg :: CompoundProg Int
complexProg = do
n <- lift $ fetchName "Fred"
fetchIntT n
But I can figure out how to write either of these functions:
runCompound :: CompoundProg a -> IO a
runCompound = undefined
runDB2T :: ( Monad m ) => DB2reqT m a -> m ( IO a )
runDB2T = undefined
I also can’t seem to find any examples. Is a free monad stack a bad way to go about doing this sort of thing?
1
Based on the example Daniel gave in the comments, here’s an answer in case anyone stumbles here:
runDB2T :: ( Monad m ) => ( forall b. m b -> IO b ) -> DB2reqT m a -> IO a
runDB2T f = iterT runDB2reqF . hoistFreeT f
runCompound :: CompoundProg a -> IO a
runCompound = runDB2T runDB1
with {-# LANGUAGE RankNTypes #-}
.