简体   繁体   中英

How does the ask function know the enrivonment to return in the Reader monad?

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

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.

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

don't define 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

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM