vendredi 23 janvier 2015

How to make an interpreter for a stack of Free Monad Transformers


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?





Aucun commentaire:

Enregistrer un commentaire