读者单子的目的是什么? [英] What is the purpose of the reader monad?

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

问题描述

reader monad 太复杂了,好像没什么用.如果我没记错的话,在像 Java 或 C++ 这样的命令式语言中,对于 reader monad 没有等效的概念.

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?

推荐答案

不要害怕!reader monad 其实并没有那么复杂,并且具有真正易于使用的实用程序.

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

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

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

  1. monad 做什么?它配备了哪些操作?它有什么用?
  2. monad 是如何实现的?它从哪里产生?

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

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

data Reader env a

这样

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

那么我们如何使用它呢?好吧,reader monad 很适合通过计算传递(隐式)配置信息.

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

任何时候,当您在计算中的各个点都需要一个常数"时,但实际上您希望能够使用不同的值执行相同的计算,那么您应该使用 reader monad.

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 monad 也用于执行面向对象的人们所说的依赖注入.例如,negamax 算法经常(以高度优化的形式)用于计算 a在两人游戏中的位置.尽管算法本身并不关心您正在玩什么游戏,但您需要能够确定游戏中的下一个"位置是什么,并且您需要能够判断当前位置是否是胜利位置.

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

获取现货价格.然后你可以在你的代码中调用这个字典......但是等等!那行不通!货币字典是不可变的,因此不仅在程序的生命周期内必须是相同的,而且从它被编译的那一刻起!所以你会怎么做?好吧,一种选择是使用 Reader monad:

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

好的,所以 Haskell 和其他函数式语言都是基于 lambda 演算.Lambda 演算的语法类似于

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, let's write the interpreter:

interp' :: Term -> Reader Env Value
--when we have a 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 [])

就是这样.一个全功能的 lambda 演算解释器.

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

另一种思考方式是问:它是如何实现的?答案是 reader monad 实际上是所有 monad 中最简单、最优雅的一种.

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 只是函数的一个奇特名称!我们已经定义了 runReader 那么 API 的其他部分呢?好吧,每个 Monad 也是一个 Functor:

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

现在,获取一个 monad:

Now, to get a monad:

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

这并没有那么可怕.ask 真的很简单:

which is not so scary. ask is really simple:

ask = Reader $ x -> x

虽然 local 还不错:

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

好的,所以 reader monad 只是一个函数.为什么有 Reader?好问题.其实,你不需要它!

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

这些更简单.更何况ask只是idlocal只是函数组合,函数顺序切换!

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

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

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