Reputation: 2176
I'm reading this example on the Reader monad: https://blog.ssanj.net/posts/2014-09-23-A-Simple-Reader-Monad-Example.html that has this code:
import Control.Monad.Reader
tom :: Reader String String
tom = do
env <- ask -- gives you the environment which in this case is a String
return (env ++ " This is Tom.")
I understood what it does but I do not understand how ask
can return anything.
In Haskell, functions can act on many different types. ask
is a function that has no input and returns the monad environment m r
. I'm trying to understand what happens when I do
(runReader tom) "Who is this?"
Somehow, runReader
will call tom
, but how is it possible that the ask
inside tom
will be able to return an env
with the text Who is this?
?
Upvotes: 3
Views: 350
Reputation: 52280
I think you basically struggle with the definition of Reader
and how it is used.
So let's quickly build our own:
newtype MyReader env a = MyReader (env -> a)
that's right a Reader
can be just a function that takes the current environment and returns some a
- here in a newtype
wrapper so we can define some instances on it without much extensions
runReader
is really simple - it just applies the given environment to our wrapped function:
runReader :: MyReader env a -> env -> a
runReader (MyReader f) = f
that should probably answer most of your question - runReader
will not call tom
in your example - you provide it as an argument so you call the wrapped function with tom
;)
But let's finish our Monad
So let's start by making this a functor - GHC by now could derive it but I guess for understanding doing it by hand is better:
instance Functor (MyReader env) where
fmap f (MyReader rdr) = MyReader (f . rdr)
if you look closely you'll see that this is indeed just function composition - so the mapped reader will take an env
, apply the wrapped function to it and after this f
to this outcome.
Applicative is next:
instance Applicative (MyReader env) where
pure a = MyReader (\_ -> a)
(MyReader f_a_to_b) <*> (MyReader f_a) = MyReader (\env -> f_a_to_b env (f_a env))
This should be easy enough - pure a
is a reader that ignores the environment and returns the value given to pure
no matter what.
And you <*>
two readers by providing the environment to both (I hope the naming helps - the first argument is a MyReader env (a -> b)
the second a MyReader env a
and the outcome should be MyReader env b
)
Now we can define the monad instance:
instance Monad (MyReader env) where
(MyReader f_a) >>= f_a_MyR_b =
MyReader (\env -> let a = f_a env in runReader (f_a_MyR_b a) env)
So this (as the applicative) passes a environment provided to the resulting MyReader
to both parts. Here using runReader
to make it a bit simpler (didn't want to unpack yet again).
Now for ask
this one seems a bit special: it will bring the passed environment right into the monad. You could probably figure it out from it's type: ask :: MyReader env env
try! but here it is:
ask :: MyReader env env
ask = MyReader (\env -> env)
So back to your question:
How ask can return anything?
it does only when really when you runReader
it:
Take this simple example:
tom :: Reader String String
tom = do
env <- ask -- gives you the environment which in this case is a String
return (env ++ " This is Tom.")
it desugars to:
tom = ask >>= (\a -> pure (a ++ " This is Tom."))
and so if you do runReader tom "Who is this"
and follow all the definitions above you end up with
runReader tom "Who is this"
= let a = (\env -> env) "Who is this" in (\env -> a ++ " This is Tom.") "Who is this"
= let a = "Who is this" in a ++ " This is Tom."
= "Who is this This is Tom".
Functor
/Applicative
yourself{-# LANGUAGE DeriveFunctor #-}
import Control.Monad (ap)
newtype MyReader env a =
MyReader { runReader :: env -> a }
deriving Functor
instance Applicative (MyReader env) where
pure = return
(<*>) = ap
instance Monad (MyReader env) where
return a = MyReader (const a)
(MyReader mr_a) >>= f_a_mr_b =
MyReader (\env -> let a = mr_a env in runReader (f_a_mr_b a) env)
ask :: MyReader env env
ask = MyReader id
Upvotes: 4
Reputation: 27227
Every line of the do
block in a Reader monad gets sent a copy of the environment in the background as if it had been passed in as an extra argument (and thanks to Haskell not having mutable data each one gets the same copy). The ask
function takes its copy returns it as the value as well.
It would help to replace the generic m
in the type of ask
with concrete type: ask::Reader r r
. This reads ask
is a monadic action whose value(the second r) is the same as the environment (the first r). In order for ask
to be useful a Reader r
context must be present (that is what the do
block is there for). Within that context r
is always sitting there waiting to be used.
Think for a moment about how you might write this example without the Monad:
tom :: String -> String
tom env = env ++ " This is Tom."
jerry :: String -> String
jerry env = env ++ " This is Tom."
tomAndJerry :: String -> String
tomAndJerry env =
let
t = tom env
j = jerry env
in (t ++"\n"++j)
Notice how env
gets manually passed to each function? (This is by the way exactly what you get if you substitute the definition of Reader r
, ask
and runReader
into the example.) The Reader monad simply wraps up the process of collecting and passing on the environment for us which is nice because it is a process we are prone to mess up (at least I am).
Reader Monads are really handy when you have a configuration file you read once at start up and then reference throughout the rest of the program. In imperative languages you might make it a global const variable. In Haskell you could try to pass that value into every function that needed it, but it is easier and less bug prone to dump it into a Reader monad (and the result is exactly the same just without all the typing.) This comes with this side benefit of the type system tagging all the functions that depend on the config file so when the configuration format changes the compiler can point out all the places that might need to be updated.
Upvotes: 6