Reader Monad的目的是什么? [英] What is the purpose of the Reader Monad?

查看:158
本文介绍了Reader Monad的目的是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Reader Monad非常复杂,似乎没用。在像Java或C ++这样的命令式语言中,没有与读者monad等价的术语(如果我是对的)。

你能给我一个简单的例子,并且让我清楚一点点?

解决方案

不要害怕!阅读器monad实际上并不复杂,并且具有真正简单易用的实用程序。

有两种方法可以处理monad:我们可以询问


  1. monad 做什么 ?它配备了什么操作?有什么好处?

  2. monad是如何实现的?从哪里出现?

从第一种方法来看,monad是一些抽象类型

  data Reader env a 



   - 阅读器是一个monad 
实例Monad(Reader env)

- 并且我们有一个函数来获得它的环境
ask :: Reader env env

- 最后,我们可以运行一个Reader
runReader :: Reader env a - > env - > a

那么我们如何使用它呢?那么,读者monad就可以通过计算来传递(隐含的)配置信息。

任何时候,如果您在各个点都需要计算中有一个常量,但是您确实希望能够使用不同的值执行相同的计算,那么你应该使用一个阅读器monad。



阅读器monad也被用来做OO人称之为依赖注入。例如,经常使用 negamax 算法(以高度优化的形式)来计算双人游戏中某个位置的价值游戏。虽然算法本身并不关心你在玩什么游戏,除了你需要能够确定游戏中的下一个位置,并且你需要能够判断当前位置是否是胜利的位置。

  import Control.Monad.Reader 

数据GameState = NotOver | FirstPlayerWin | SecondPlayerWin |联系

数据游戏位置
=游戏{
getNext :: position - > [position],
getState :: position - > GameState
}

getNext':: position - >阅读器(游戏位置)[位置]
getNext'位置
=做游戏< - 请求
返回$ getNext游戏位置

getState':: position - > ;阅读器(游戏位置)GameState
getState'position
=做游戏< - 请求
返回$ getState游戏位置


negamax :: Double - >位置 - >阅读器(游戏位置)Double
negamax颜色位置
=执行状态< - getState'位置
案例状态
FirstPlayerWin - >返回颜色
SecondPlayerWin - >返回$ negate color
Tie - >返回0
NotOver - >可能< - getNext'位置
值< - mapM((liftM negate)。negamax(negate color))可能
返回$最大值

然后这将与任何有限的,确定性的双人游戏一起使用。

即使对于并非真正依赖注入的东西,此模式也很有用。假设你在财务部门工作,你可能会设计一些复杂的定价资产的逻辑(衍生品说),这是一切都很好,你可以做到没有任何发臭的单子。但是,你修改程序来处理多种货币。您需要能够在不同货币之间进行转换。您的第一个尝试是定义一个顶级函数

  type CurrencyDict = Map CurrencyName美元
currencyDict :: CurrencyDict

即可获得现货价格。然后你可以在你的代码中调用这个字典....但是等等!那不行!货币字典是不可改变的,因此不仅适用于程序的生命周期,而且必须从获得编译的时间!所以你会怎么做?一种选择是使用Reader monad:

  computePrice :: Reader CurrencyDict Dollars 
computePrice
= do currencyDict< - 要求
- 在这里插入计算

也许最经典的用法 - 正在实施口译员。但是,在我们看之前,我们需要引入另一个函数

  local ::(env  - > env) - > ; Reader env a  - >阅读器env a 

好的,所以Haskell和其他函数式语言基于 lambda微积分。 Lambda微积分的语法如下所示:

  data Term = Apply Term Term | Lambda字符串术语| Var Term deriving(显示)

我们希望为此语言编写一个评估程序。为此,我们需要跟踪一个环境,这是一个与条款相关的绑定列表(实际上它会因为我们想要进行静态范围设定而关闭)。

  newtype Env = Env([(String,Closure)])
类型Closure =(Term,Env)

当我们完成后,我们应该得出一个值(或一个错误):

  data Value = Lam字符串关闭|失败字符串

所以,让我们编写解释器:

  interp':: Term  - >读者Env价值
- 当我们有lambda期限时,我们可以返回它
interp'(lambda nv t)
= do env < - ask
return $ Lam nv (t,env)
- 当我们遇到一个值时,我们在环境中查找它
interp'(Var v)
= do(Env env)< - ask
case lookup(show v)env of
- 如果它不在环境中,我们遇到问题
Nothing - >返回。失败$unbound变量:++(show v)
- 如果它在环境中,比我们应该解释它
Just(term,env) - > local(const env)$ interp'term
- 复杂的情况是一个应用程序
interp'(应用t1 t2)
= do v1 < - interp't1
case v1
失败s - >返回(失败)
Lam nv clos - >本地(\(Env ls) - > Env((nv,clos):ls))$ interp't2
- 我猜不那么复杂!

最后,我们可以通过传递一个简单的环境来使用它:

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

是吗。 lambda演算的全功能解释器。




所以,想想这个问题的另一种方法是问:它是如何实现的?那么答案是,读者monad实际上是所有monads中最简单最优雅的一种。

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

阅读器只是功能的一个奇特名称!我们已经定义了 runReader 那么API的其他部分呢?那么每一个 Monad 也是一个 Functor

 实例Functor(Reader env)其中
fmap f(Reader g)= Reader $ f。 g

现在,要获得monad:

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

这并不那么可怕。 ask 非常简单:

  ask = Reader $ \x  - > x 

while local 并不那么糟糕。

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

好的,所以读者monad只是一个函数。为什么有读者?好问题。实际上,你并不需要它!

  instance Functor(( - >)env)其中
fmap = (。)

实例Monad(( - >)env)其中
return = const
f>> = g = \ x - > g(f x)x

这些更简单。此外, ask 只是 id local 是只是在其他订单中的功能组合!


The Reader Monad is so complex and seems to be useless. In imperative language like Java or C++, there is no equivalent term for reader monad (if I am right).

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

解决方案

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 at 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, than 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.


So, the other way to think about this is to ask: how is it implemented? Well 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 is more, ask is just id and local is just function composition in the other order!

这篇关于Reader Monad的目的是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆