Fine-grained IO with mtl Simplified
Posted on 2018-03-02 by xkollar
.In previous article I have provided
example of how to do selectively add “impure” actions into mtl monadic stack
without full power of MonadIO
. There I have defined separate newtype
wrappers, which is useful if you want to have more control and flexibility over
possible interpreters. Most of the time however, one just wants to have simpler
code. If that is what you are aiming for, you can have just one wrapper newtype
and provide all instances for this particular wrapper.
Diff.
@@ -27,18 +27,12 @@ class WriteFile m where
writeFile :: FilePath -> String -> m ()
-- NEWTYPES --------------------------------------newtype ReadFileT m a = ReadFile { runReadFile :: m a }
- deriving (Functor, Applicative, Monad, MonadIO)
-
-newtype WriteFileT m a = WriteFile { runWriteFile :: m a }
+newtype SimpleIORunnerT m a = SimpleIORunner { runSimpleRunner :: m a }
deriving (Functor, Applicative, Monad, MonadIO)
-- INSTANCES -------------------------------------instance MonadTrans ReadFileT where
- lift = ReadFile
-
-instance MonadTrans WriteFileT where
- lift = WriteFile
+instance MonadTrans SimpleIORunnerT where
+ lift = SimpleIORunner
instance {-# OVERLAPPABLE #-} (Monad m, MonadTrans t, ReadFile m) => ReadFile (t m) where
readFile = lift . readFile@@ -46,10 +40,10 @@ instance {-# OVERLAPPABLE #-} (Monad m, MonadTrans t, ReadFile m) => ReadFile (t
instance {-# OVERLAPPABLE #-} (Monad m, MonadTrans t, WriteFile m) => WriteFile (t m) where
writeFile fp = lift . writeFile fp
-instance {-# OVERLAPS #-} MonadIO m => ReadFile (ReadFileT m) where
+instance {-# OVERLAPS #-} MonadIO m => ReadFile (SimpleIORunnerT m) where
readFile = liftIO . IO.readFile
-instance {-# OVERLAPS #-} MonadIO m => WriteFile (WriteFileT m) where
+instance {-# OVERLAPS #-} MonadIO m => WriteFile (SimpleIORunnerT m) where
writeFile fp = liftIO . IO.writeFile fp
-- EXAMPLE --------------------------------------@@ -68,7 +62,7 @@ combinedAction :: (MonadReader What m, ReadFile m, WriteFile m) => m ()
combinedAction = readAction >>= writeAction
example1 :: IO ()-example1 = runWriteFile . runReadFile $ runReaderT combinedAction conf
+example1 = runSimpleRunner $ runReaderT combinedAction conf
where conf = What "/etc/resolv.conf" "/dev/stdout"
Full code.
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Main (main) where
import Control.Applicative (Applicative, pure, (<*>))
import Control.Monad (Monad, (>>=))
import Data.Function (($), (.), flip, const)
import Data.Functor (Functor, fmap)
import Data.String (String)
import System.IO (FilePath, IO)
import qualified System.IO as IO (readFile, writeFile)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Reader (MonadReader, asks, runReaderT)
import Control.Monad.Trans (MonadTrans, lift)
-- CLASSES --------------------------------------
class ReadFile m where
readFile :: FilePath -> m String
class WriteFile m where
writeFile :: FilePath -> String -> m ()
-- STACK ----------------------------------------
instance {-# OVERLAPPABLE #-} (Monad m, MonadTrans t, ReadFile m) => ReadFile (t m) where
readFile = lift . readFile
instance {-# OVERLAPPABLE #-} (Monad m, MonadTrans t, WriteFile m) => WriteFile (t m) where
writeFile fp = lift . writeFile fp
-- NEWTYPES and basic INSTANCES -----------------
newtype SimpleIORunnerT m a = SimpleIORunner { runSimpleRunner :: m a }
deriving (Functor, Applicative, Monad, MonadIO)
instance MonadTrans SimpleIORunnerT where
= SimpleIORunner
lift
-- IMPLEMENTATIONS ------------------------------
instance {-# OVERLAPS #-} MonadIO m => ReadFile (SimpleIORunnerT m) where
readFile = liftIO . IO.readFile
instance {-# OVERLAPS #-} MonadIO m => WriteFile (SimpleIORunnerT m) where
writeFile fp = liftIO . IO.writeFile fp
-- EXAMPLE --------------------------------------
data What = What
from :: FilePath
{ to :: FilePath
,
}
readAction :: (MonadReader What m, ReadFile m) => m String
= asks from >>= readFile
readAction
writeAction :: (MonadReader What m, WriteFile m) => String -> m ()
= asks to >>= flip writeFile c
writeAction c
combinedAction :: (MonadReader What m, ReadFile m, WriteFile m) => m ()
= readAction >>= writeAction
combinedAction
main :: IO ()
= runSimpleRunner $ runReaderT combinedAction conf
main where
= What "/etc/resolv.conf" "/dev/stdout" conf