Reader Monad

2017-04-08  本文已影响0人  QuRyu

本文使用Haskell语言,并需要读者有Monad的基本概念

什么是Reader Monad

在介绍Reader之前,我们先看看Reader的函数签名:


newtype Reader e a = Reader {

runReader :: e -> a

}

类似于State, Reader内部包含的也是一个函数;但不同之处为State在除了输出结果外还会生成一个新的state,而Reader只是单纯的输出一个结果。

如果不能像State那样实现状态的传递,那么Reader有什么用呢?我们可以先看看下面这个例子。


readString :: Reader String [String]

readString = do

e <- ask  -- 获取环境变量

e' <- local ((++) "hi! ") ask  -- 获取并修改环境变量

e'' <- ask -- 获取环境变量

return (e : e' : e'' : [])

ghci> unline runReader readString "abc"

abc

hi! abc

abc

ask的作用类似于State的get,而local则类似于put。这两个函数签名如下:


ask :: Reader r r

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

从上述例子中可以看到,即使调用了local方法,但是每次从环境中(也就是Reader签名中的e)获取的变量都不会改变。所以,Reader的作用大致可以理解为:提供给一个或多个绑定在一起的计算相同的输入值。

Reader的简单使用

在深入了解Reader和辅助函数的实现之前,先看一个来自官方文档的例子。


type Bindings = Map String Int

isCountCorrect :: Bindings -> Bool

isCountCorrect = runReader calc_count -- point-free

calc_count :: Reader Bindings Bool

calc_count = do

bindings <- ask

let count = lookupVar "count" bindings

return (count == (Map.size bindings))

lookupVar :: String -> Bindings -> Int

lookupVar s = fromJust . Map.lookup s  -- point-free

sampleBindings :: Bindings

sampleBindings = Map.fromList [("abc", 1), ("cbd", 2), ("count", 3)]

ghci> isCountCorrect sampleBindings

True

isCountCorrect通过使用calc_count这个Reader获取结果,所以我们来分析下calc_count到底做了什么。

calc_count的签名为Reader Binding Bool,也就是


Reader Binding Bool = Reader {

runReader :: Binding -> Bool

}

在接受类型为Binding的变量后,calc_count会返回Bool变量。这是从外部看calc_count的行为,那它具体做了什么呢?

首先,调用ask函数获取周围环境,也就是获得Binding变量;接着,调用lookupVar函数来获取Map中key为"count"的值,也就是3。最后,对count变量和Map的长度进行比较,返回Bool值。

lookupVar函数则是根据给定的key来查询值是否存在,如果存在就返回,不存在就抛出异常。

Reader实现

了解Reader的结构和基本使用方法后,将Reader声明为Monad、Applicative等类的instance以获取更抽象的表达能力。


instance Monad (Reader e) where

return a = Reader $ const a

m >>= f = Reader $ \e ->

runReader (f $ runReader m e) e

instance Applicative (Reader e) where

pure a = Reader $ const a

f <*> m = Reader $ \e ->

runReader f e $ runReader m e

instance Functor (Reader e) where

fmap f m = Reader $ \e -> f $ runReader m e

除此之外,Reader还有一系列的辅助函数,比如askaskslocal等。


ask :: Reader r r

ask = Reader $ \e -> e

asks :: (r -> a) -> Reader r a

asks f = do

e <- ask

return $ f e

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

local f r = do

e <- ask

return $ runReader r (f e)

如之前所说,ask的作用是从获取环境变量,也就是elocal是更改一个Reader的环境变量,但不会local所在的Reader中其他操作获取到的环境变量。而asks的作用可由一个例子来展示:


ghci> runReader (asks length) “hi”

2

Reader in Haskell

Haskell中的Reader和State一样,是通过transformer来实现的。ReaderT和Reader的函数签名是:


newtype ReaderT r m a = ReaderT {

runReaderT :: r -> m a

}

type Reader e a = ReaderT e Identity a

更多的使用例子和ReaderT的实现细节请分别参照

  1. https://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Reader.html

  2. https://hackage.haskell.org/package/transformers-0.5.4.0/docs/Control-Monad-Trans-Reader.html

上一篇下一篇

猜你喜欢

热点阅读