本文使用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还有一系列的辅助函数,比如ask、asks、local等。
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的作用是从获取环境变量,也就是e;local是更改一个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的实现细节请分别参照












网友评论