module Main (main) where
import Control.Monad.Reader
p1 :: String -> IO ()
p1 = putStrLn . ("Apple "++)
p2 :: String -> IO ()
p2 = putStrLn . ("Pear "++)
main :: IO ()
main = do
p1 "x"
p2 "y"
r "z"
r :: String -> IO ()
r = do
p1
p2
It prints:
Apple x
Pear y
Pear z
Why?
The problem is in
r. Given the following definition ofReadermonad:We can simplify
r:This also shows that
Reader‘s(>>)is justconstwith a bit more specific type.If you want to distribute the environment and then execute both actions, you have to bind the result of applying
p1to the environment, for example:Or using
Applicative:Expanding on the
Readerpart,Control.Monad.Readerprovides three variants ofReader.(->) e, which is what the functionrusesReaderT e m, a newtype wrapper for functions of typee -> m aReader e, defined in terms ofReaderTasReaderT e IdentityWithout any further information, the implicit
(->) ewill be used. Why?The overall type of
doblock is given by the last expression, which is also constrained to be of the formMonad m => m afor somemanda.Looking back at
r, it’s clear that thedoblock has a typeString -> IO ()as given by the type ofrand alsop2. It also requiresString -> IO ()to beMonad m => m a. Now, unifying these two types:This matches
(->) emonad instance by choosinge = String.Being a monad transformer,
ReaderTtakes care of the inner plumbing to make sure the actions of the inner monad are properly sequenced and executed. To selectReaderT, it is necessary to explicitly mention it (usually in a type signature, but functions which fix the type to beReaderT, such asrunReaderT, also work):This comes with another problem,
p1andp2have a typeString -> IO (), which doesn’t match the requiredReaderT String IO ().The ad-hoc solution (tailored exactly for this situation), is just to apply
To obtain something more general,
MonadIOtype class can liftIOactions into the transformer andMonadReadertype class allows accessing the environment. These two type classes work as long as there isIO(orReaderTrespectively) somewhere in the transformer stack.Or more concisely:
Regarding your question in comments, you can implement the relevant instances in this way:
The actual typeclass can be found in the
mtlpackage (package, type class), the newtype andMonadinstance intransformerspackage (package, type class).As for making a
e -> m aMonadinstance, you are out of luck.Monadrequires a type constructor of kind* -> *, which means we are attempting to do something like this (in pseudo-code):where
/\stands for type-level lambda. However, the closest thing we can get to a type level lambda is a type synonym (which must be fully applied before we can make type class instances, so no luck here) or a type family (which cannot be used as an argument to type class either). Using something like(->) e . mleads tonewtypeagain.