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
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"