data T b = E | N b (T b) (T b)
f :: T b -> Reader Int (T Int)
f (N i l r) = ask >>= \x -> local ((-)4) (f l) >>= \l' -> local ((-)1) (f r) >>= \r' -> return (N x l' r')
f E = return E
I have a problem with understanding how this code works. Especially, how does ask
know where the environment is (in our case just Int
)?
To be more precise: I am an imperative programmer and in such languages it is easy. Methods can be called on any object like: obj.f()
, or we have to pass data by argument when we want function use external data.
That's kind of what the Reader monad does ; it gives you an ask
function, which "magically" pops up a value out of thin air. To actually use this, you need to call runReader
, and give it the Int
environment. The Reader
type then automatically propagates that from the runReader
call to each of the ask
calls.
Short, hand-wavy answer. The Reader Int (T Int)
value is essentially just a wrapped-up function of type Int -> (T Int)
. In order to run that function, we first need to extract it. We do that with runReader
.
data T b = ... deriving (Show)
main = let tree = (N 10 (N 8 E E) E)
g = f tree
h = runReader g
in print $ h 20
(Typically, you would simply write print $ runReader (f tree) 20
; I split it up to correspond to the sketchy analogy below.)
ask
(defined by the MonadReader
typeclass and as implemented by the ReaderT
monad transformer used to define the Reader
type) essentially retrieves the value of the argument passed to h
.
In some sense, the Reader Int (T Int)
is an object that contains a function g
that calls a function ask
. runReader g
creates a new function which, when called, defines a function ask
that simply returns its argument, then calls g
. Now when g
calls ask
, it gets back the argument originally passed to h
.
I recomend reading this first. Ask is defined:
ask :: (Monad m) => ReaderT r m r
ask = ReaderT return
and reader:
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
type Reader r = ReaderT r Identity
so
do
r <- ask
...
is equivalent to
do
r <- ReaderT return
...
So essentially <-
just reaches into the identity monad, and grabs what ever value that will eventually be lifted by runReader R = return
.
This enables global variables in haskell.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.