In part 1 I went over the basics of the Reader Monad. In this post I’ll cover some more advanced topics and alternatives to the Reader Monad.

Composable MonadReader

In my prior post went over how to make a composable MonadReader using the Has pattern. We will apply that technique to our example from part one:

class HasFilePath a where

hasFilePath :: a -> FilePath



load :: (HasFilePath a, MonadReader a m, MonadIO m)

=> String -> m String

load x = do

config <- asks hasFilePath

liftIO $ readFile $ config ++ x loadRevision :: (HasFilePath a, MonadReader a m, MonadIO m)

=> Int -> m String

loadRevision x = load $ “history” ++ show x ++ “.txt” loadAll :: (HasFilePath a, MonadReader a m, MonadIO m)

=> Int -> String -> m (String, String)

loadAll x y = do

a <- loadRevision x

b <- load y

return (a, b)

Lets say you follow the same pattern with a different reader environment:

class HasPrintCount a where

hasPrintCount :: a -> Int awesomeMessage :: (HasPrintCount a, MonadReader a m, MonadIO m)

=> m ()

awesomeMessage = do

repeatCount <- asks hasPrintCount

replicateM_ repeatCount $ liftIO $ putStrLn “hello!”

No we can write a function that depends on a FilePath and Int in the environment.

veryAwesomeLoader :: ( HasPrintCount a

, HasFilePath a

, MonadReader a m

, MonadIO m

)

=> String -> IO String

veryAwesomeLoader filePath = do

awesomeMessage

load filePath

It is worth noting that we can accomplish the same composable functions with ReaderT instead of MonadReader but MonadReader is more general.

IO is Easy … Except When it is Not

So far we have seen some examples of using MonadIO . It looks pretty easy, one just calls liftIO . This mostly works but if we want to liftIO a function like:

withSystemTempDirectory :: String -> (FilePath -> IO a) -> IO a

and convert it to:

withSystemTempDirectory’ :: MonadIO m

=> String -> (FilePath -> m a) -> m a

liftIO can’t help us here.

We need some stronger and unfortunately a lot more complicated. I speak of MonadBaseControl . Here we go.

withSystemTempDirectory' :: MonadBaseControl IO m

=> String -> (FilePath -> m a) -> m a

withSystemTempDirectory' template tempFileAction = control $

\lowerToIO -> withSystemTempDirectory template $ \tmpFilePath ->

lowerToIO $ tempFileAction tmpFilePath

So here control gives a function that can be used to convert MonadBaseControl IO m => m a -> IO (StM m a) … anyway don’t worry about the type function StM , for reader it is just a anyway.

This actually shows up a fair amount. forkIO , bracket , and many other examples. Luckily there is a package called lifted-base which does the dance shown above for all of base .

Alternatives

Clearly there are benefits to using the Reader Monad, but there are also downsides. We will have to use monad-control which is complex. The Reader Monad requires writing code in monadic style, but applicative style is easier. It would wise to consider alternatives.

Threading

The first alternative to a Reader Monad is go back to what we started with, e.g. just manually threading the environment.

ImplicitParams

ImplicitParams is an extension in GHC to provide dynamically bound variables. Using implicit parameters our running example would look like:

-- Imagine this is a directory

type Config = FilePath load :: (?config :: Config) => String -> IO String

load x = readFile (?config ++ x) loadRevision :: (?config :: Config) => Int -> IO String

loadRevision x = load ("history" ++ show x ++ ".txt") loadAll :: (?config :: Config)

=> Int -> String -> IO (String, String)

loadAll x y = do

a <- load y

b <- loadRevision x

return (a, b)

So what’s changed? Well we no longer have to use MonadIO which means we also don’t have to worry about using MonadBaseControl . That’s a major win, because MonadBaseControl is painful to use.

Our code is also slightly simpler, because we can retrieve the value of the config with ? and we do need <- or >>= . So even though we already where writing monadic code, it was still simpler to use implicit parameters.

Implicit parameters take on the most recently bound value, which similar to local :

testConfig :: (?config :: String) => Int -> IO ()

testConfig x = putStrLn $ ?config ++ show x testLocal = do

let ?config = "hey "

in do

testConfig 1

testConfig 2



let ?config = "you "

in do

testConfig 3

testConfig 4

will print:

hey 1

hey 2

you 3

you 4

Implicit parameters do not require a monad:

approx :: (?tolerance :: Double) => Double -> Double -> Bool

approx x y = abs (x - y) < ?tolerance

A neat use for implicit parameters would be to configure instances:

newtype Approx = Approx Double



instance (?tolerance :: Double) => Eq Approx where

Approx x == Approx y = abs (x - y) < tolerance

Although this code is not allowed.

Implicit parameters have some gotchas. The behavior of functions can change with the addition of a type signature. See this example in the GHC manual for more details.

In general Haskellers avoid implicit parameters because they make the code harder to reason about … or maybe they just don’t know about them.

reflection

Edward Kmett has a library called reflection which provides an alternative method of passing configuration information.

It offers two different configurations methods. One uses reify and reflect and as alternative to Monad Reader does not provide much over directly threading the config. I’m going to focus on the simpler Given type class.

Given is a magical type class which takes advantage of GHC’s internal representation to work correctly. It is neither portable nor guaranteed to work in future versions of GHC.

Given is similar to implicit parameters. One advantage Given has over implicit parameters is you can use it to configure instances:

newtype Approx = Approx Double



instance Given Double => Eq Approx where

Approx x == Approx y = abs (x - y) < given

As it currently stands in GHC 8.0, the give in the upper most scope wins:

> give (1 :: Int) $ (given :: Int) + given

2

> give (1 :: Int) $ given + give (2 :: Int) (given :: Int)

2

This is the exact opposite behavior from ImplicitParams :

> let ?x = 1 in ?x + ?x

2

> let ?x = 1 in ?x + let ?x = 2 in ?x

3

The documentation explains having more than one value for a Given at time is implementation defined (e.g. defined by GHC), and it is not clear what the behavior will be when instance resolution is involved.

In general one should only use Given if you can assure give is only called once.

Conclusion

The Reader Monad can be extended to complex use cases with the Has pattern and monad-control . However, it comes at a cost. ImplicitParams and reflection offer alternatives for dealing with configurations, without requiring monadic style, liftIO or MonadBaseControl . They are simpler to use, but they’re implementation is tied closely to GHC and it is unclear if their behavior is predictable when part of a large program. Finally, sometimes it is best to just stick with the tedium of threading state.