Fine-grained IO with mtl Simplified
Posted on 2018-03-02 by xkollar .
In the previous article
I have provided an example of how to selectively add “impure” actions into mtl
monadic stack without the full power of MonadIO. 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 newtype wrapper and provide all the 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
lift = SimpleIORunner
-- 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
readAction = asks from >>= readFile
writeAction :: (MonadReader What m, WriteFile m) => String -> m ()
writeAction c = asks to >>= flip writeFile c
combinedAction :: (MonadReader What m, ReadFile m, WriteFile m) => m ()
combinedAction = readAction >>= writeAction
main :: IO ()
main = runSimpleRunner $ runReaderT combinedAction conf
where
conf = What "/etc/resolv.conf" "/dev/stdout"