What is the purpose of the reader monad?

我怕爱的太早我们不能终老 提交于 2019-11-26 12:34:37

问题


The reader monad is so complex and seems to be useless. In an imperative language like Java or C++, there is no equivalent concept for the reader monad, if I am not mistaken.

Can you give me a simple example and clear this up a little bit?


回答1:


Don't be scared! The reader monad is actually not so complicated, and has real easy-to-use utility.

There are two ways of approaching a monad: we can ask

  1. What does the monad do? What operations is it equipped with? What is it good for?
  2. How is the monad implemented? From where does it arise?

From the first approach, the reader monad is some abstract type

data Reader env a

such that

-- Reader is a monad
instance Monad (Reader env)

-- and we have a function to get its environment
ask :: Reader env env

-- finally, we can run a Reader
runReader :: Reader env a -> env -> a

So how do we use this? Well, the reader monad is good for passing (implicit) configuration information through a computation.

Any time you have a "constant" in a computation that you need at various points, but really you would like to be able to perform the same computation with different values, then you should use a reader monad.

Reader monads are also used to do what the OO people call dependency injection. For example, the negamax algorithm is used frequently (in highly optimized forms) to compute the value of a position in a two player game. The algorithm itself though does not care what game you are playing, except that you need to be able to determine what the "next" positions are in the game, and you need to be able to tell if the current position is a victory position.

 import Control.Monad.Reader

 data GameState = NotOver | FirstPlayerWin | SecondPlayerWin | Tie

 data Game position
   = Game {
           getNext :: position -> [position],
           getState :: position -> GameState
          }

 getNext' :: position -> Reader (Game position) [position]
 getNext' position
   = do game <- ask
        return $ getNext game position

 getState' :: position -> Reader (Game position) GameState
 getState' position
   = do game <- ask
        return $ getState game position


 negamax :: Double -> position -> Reader (Game position) Double
 negamax color position
     = do state <- getState' position 
          case state of
             FirstPlayerWin -> return color
             SecondPlayerWin -> return $ negate color
             Tie -> return 0
             NotOver -> do possible <- getNext' position
                           values <- mapM ((liftM negate) . negamax (negate color)) possible
                           return $ maximum values

This will then work with any finite, deterministic, two player game.

This pattern is useful even for things that are not really dependency injection. Suppose you work in finance, you might design some complicated logic for pricing an asset (a derivative say), which is all well and good and you can do without any stinking monads. But then, you modify your program to deal with multiple currencies. You need to be able to convert between currencies on the fly. Your first attempt is to define a top level function

type CurrencyDict = Map CurrencyName Dollars
currencyDict :: CurrencyDict

to get spot prices. You can then call this dictionary in your code....but wait! That won't work! The currency dictionary is immutable and so has to be the same not only for the life of your program, but from the time it gets compiled! So what do you do? Well, one option would be to use the Reader monad:

 computePrice :: Reader CurrencyDict Dollars
 computePrice
    = do currencyDict <- ask
      --insert computation here

Perhaps the most classic use-case is in implementing interpreters. But, before we look at that, we need to introduce another function

 local :: (env -> env) -> Reader env a -> Reader env a

Okay, so Haskell and other functional languages are based on the lambda calculus. Lambda calculus has a syntax that looks like

 data Term = Apply Term Term | Lambda String Term | Var Term deriving (Show)

and we want to write an evaluator for this language. To do so, we will need to keep track of an environment, which is a list of bindings associated with terms (actually it will be closures because we want to do static scoping).

 newtype Env = Env ([(String,Closure)])
 type Closure = (Term, Env)

When we are done we should get out a value (or an error):

 data Value = Lam String Closure | Failure String

So, lets write the interpreter:

interp' :: Term -> Reader Env Value
--when we have lambda term, we can just return it
interp' (Lambda nv t)
   = do env <- ask
        return $ Lam nv (t, env)
--when we run into a value we look it up in the environment
interp' (Var v)
   = do (Env env) <- ask
        case lookup (show v) env of
          -- if it is not in the environment we have a problem
          Nothing -> return . Failure $ "unbound variable: " ++ (show v)
          -- if it is in the environment, then we should interpret it
          Just (term, env) -> local (const env) $ interp' term
--the complicated case is an application
interp' (Apply t1 t2)
   = do v1 <- interp' t1
        case v1 of
           Failure s -> return (Failure s)
           Lam nv clos -> local (\(Env ls) -> Env ((nv,clos):ls)) $ interp' t2
--I guess not that complicated!

Finally, we can use it by passing a trivial environment:

interp :: Term -> Value
interp term = runReader (interp' term) (Env [])

And that is it. A fully functional interpreter for the lambda calculus.


The other way to think about this is to ask: How is it implemented? The answer is that the reader monad is actually one of the simplest and most elegant of all monads.

newtype Reader env a = Reader {runReader :: env -> a}

Reader is just a fancy name for functions! We have already defined runReader so what about the other parts of the API? Well, every Monad is also a Functor:

instance Functor (Reader env) where
   fmap f (Reader g) = Reader $ f . g

Now, to get a monad:

instance Monad (Reader env) where
   return x = Reader (\_ -> x)
   (Reader f) >>= g = Reader $ \x -> runReader (g (f x)) x

which is not so scary. ask is really simple:

ask = Reader $ \x -> x

while local isn't so bad.

local f (Reader g) = Reader $ \x -> runReader g (f x)

Okay, so the reader monad is just a function. Why have Reader at all? Good question. Actually, you don't need it!

instance Functor ((->) env) where
  fmap = (.)

instance Monad ((->) env) where
  return = const
  f >>= g = \x -> g (f x) x

These are even simpler. What's more, ask is just id and local is just function composition with the order of the functions switched!




回答2:


I remember being puzzled as you were, until I discovered on my own that variants of the Reader monad are everywhere. How did I discover it? Because I kept writing code that turned out to be small variations on it.

For example, at one point I was writing some code to deal with historical values; values that change over time. A very simple model of this is functions from points of time to the value at that point in time:

import Control.Applicative

-- | A History with timeline type t and value type a.
newtype History t a = History { observe :: t -> a }

instance Functor (History t) where
    -- Apply a function to the contents of a historical value
    fmap f hist = History (f . observe hist)

instance Applicative (History t) where
    -- A "pure" History is one that has the same value at all points in time
    pure = History . const

    -- This applies a function that changes over time to a value that also 
    -- changes, by observing both at the same point in time.
    ff <*> fx = History $ \t -> (observe ff t) (observe fx t)

instance Monad (History t) where
    return = pure
    ma >>= f = History $ \t -> observe (f (observe ma t)) t

The Applicative instance means that if you have employees :: History Day [Person] and customers :: History Day [Person] you can do this:

-- | For any given day, the list of employees followed by the customers
employeesAndCustomers :: History Day [Person]
employeesAndCustomers = (++) <$> employees <*> customers

I.e., Functor and Applicative allow us to adapt regular, non-historical functions to work with histories.

The monad instance is most intuitively understood by considering the function (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c. A function of type a -> History t b is a function that maps an a to a history of b values; for example, you could have getSupervisor :: Person -> History Day Supervisor, and getVP :: Supervisor -> History Day VP. So the Monad instance for History is about composing functions like these; for example, getSupervisor >=> getVP :: Person -> History Day VP is the function that gets, for any Person, the history of VPs that they've had.

Well, this History monad is actually exactly the same as Reader. History t a is really the same as Reader t a (which is the same as t -> a).

Another example: I've been prototyping OLAP designs in Haskell recently. One idea here is that of a "hypercube," which is a mapping from intersections of a set of dimensions to values. Here we go again:

newtype Hypercube intersection value = Hypercube { get :: intersection -> value }

One common of operation on hypercubes is to apply a multi-place scalar functions to corresponding points of a hypercube. This we can get by defining an Applicative instance for Hypercube:

instance Functor (Hypercube intersection) where
    fmap f cube = Hypercube (f . get cube)


instance Applicative (Hypercube intersection) where
    -- A "pure" Hypercube is one that has the same value at all intersections
    pure = Hypercube . const

    -- Apply each function in the @ff@ hypercube to its corresponding point 
    -- in @fx@.
    ff <*> fx = Hypercube $ \x -> (get ff x) (get fx x)

I just copypasted the History code above and changed names. As you can tell, Hypercube is also just Reader.

It goes on and on. For example, language interpreters also boil down to Reader, when you apply this model:

  • Expression = a Reader
  • Free variables = uses of ask
  • Evaluation environment = Reader execution environment.
  • Binding constructs = local

A good analogy is that a Reader r a represents an a with "holes" in it, that prevent you from knowing which a we're talking about. You can only get an actual a once you supply a an r to fill in the holes. There are tons of things like that. In the examples above, a "history" is a value that can't be computed until you specify a time, a hypercube is a value that can't be computed until you specify an intersection, and a language expression is a value that can't be computed until you supply the values of the variables. It also gives you an intuition on why Reader r a is the same as r -> a, because such a function is also intuitively an a missing an r.

So the Functor, Applicative and Monad instances of Reader are a very useful generalization for cases where you are modeling anything of the sort "an a that's missing an r," and allow you to treat these "incomplete" objects as if they were complete.

Yet another way of saying the same thing: a Reader r a is something that consumes r and produces a, and the Functor, Applicative and Monad instances are basic patterns for working with Readers. Functor = make a Reader that modifies the output of another Reader; Applicative = connect two Readers to the same input and combine their outputs; Monad = inspect the result of a Reader and use it to construct another Reader. The local and withReader functions = make a Reader that modifies the input to another Reader.




回答3:


In Java or C++ you may access any variable from anywhere without any problem. Problems appears when your code becomes multi-threaded.

In Haskell you have only two ways to pass the value from one function to another:

  • You pass the value through one of input parameters of the callable function. Drawbacks are: 1) you can't pass ALL the variables in that way - list of input parameters just blow your mind. 2) in sequence of function calls: fn1 -> fn2 -> fn3, function fn2 may not need parameter which you pass from fn1 to fn3.
  • You pass the value in scope of some monad. Drawback is: you have to get firm understanding what Monad conception is. Passing the values around is just one of great deal of applications where you may use the Monads. Actually Monad conception is incredible powerful. Don't be upset, if you didn't get insight at once. Just keep trying, and read different tutorials. The knowledge you'll get will pay off.

The Reader monad just pass the data you want to share between functions. Functions may read that data, but can't change it. That's all that do the Reader monad. Well, almost all. There are also number of functions like local, but for the first time you can stick with asks only.



来源:https://stackoverflow.com/questions/14178889/what-is-the-purpose-of-the-reader-monad

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!