Mi piace l'idea di usare le monade libere per "purificare" il codice e l'ho usato in alcuni semplici scenari. Tuttavia, non riesco a capire come scrivere un interprete per un trasformatore gratuito. In particolare una pila di monadi gratuiti.
Ecco un semplice esempio di ciò che ho in mente. Diciamo che sto depurando l'accesso a due database. Posso scrivere
{-# 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
che funziona bene per l'utilizzo di DB1 e DB2 separatamente. Ma se voglio usarli insieme, penserei di impilare un trasformatore con l'altro come
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
Ma posso capire come scrivere una di queste funzioni:
runCompound :: CompoundProg a -> IO a
runCompound = undefined
runDB2T :: ( Monad m ) => DB2reqT m a -> m ( IO a )
runDB2T = undefined
Anche io non riesco a trovare esempi. Un monade stack gratuito è un brutto modo di fare questo genere di cose?