本文使用Haskell語言,并需要讀者有Monad的基本概念
什么是Reader Monad
在介紹Reader之前,我們先看看Reader的函數(shù)簽名:
newtype Reader e a = Reader {
runReader :: e -> a
}
類似于State, Reader內(nèi)部包含的也是一個函數(shù);但不同之處為State在除了輸出結(jié)果外還會生成一個新的state,而Reader只是單純的輸出一個結(jié)果。
如果不能像State那樣實(shí)現(xiàn)狀態(tài)的傳遞,那么Reader有什么用呢?我們可以先看看下面這個例子。
readString :: Reader String [String]
readString = do
e <- ask -- 獲取環(huán)境變量
e' <- local ((++) "hi! ") ask -- 獲取并修改環(huán)境變量
e'' <- ask -- 獲取環(huán)境變量
return (e : e' : e'' : [])
ghci> unline runReader readString "abc"
abc
hi! abc
abc
ask的作用類似于State的get,而local則類似于put。這兩個函數(shù)簽名如下:
ask :: Reader r r
local :: (r -> r) -> Reader r a -> Reader r a
從上述例子中可以看到,即使調(diào)用了local方法,但是每次從環(huán)境中(也就是Reader簽名中的e)獲取的變量都不會改變。所以,Reader的作用大致可以理解為:提供給一個或多個綁定在一起的計(jì)算相同的輸入值。
Reader的簡單使用
在深入了解Reader和輔助函數(shù)的實(shí)現(xiàn)之前,先看一個來自官方文檔的例子。
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獲取結(jié)果,所以我們來分析下calc_count到底做了什么。
calc_count的簽名為Reader Binding Bool,也就是
Reader Binding Bool = Reader {
runReader :: Binding -> Bool
}
在接受類型為Binding的變量后,calc_count會返回Bool變量。這是從外部看calc_count的行為,那它具體做了什么呢?
首先,調(diào)用ask函數(shù)獲取周圍環(huán)境,也就是獲得Binding變量;接著,調(diào)用lookupVar函數(shù)來獲取Map中key為"count"的值,也就是3。最后,對count變量和Map的長度進(jìn)行比較,返回Bool值。
而lookupVar函數(shù)則是根據(jù)給定的key來查詢值是否存在,如果存在就返回,不存在就拋出異常。
Reader實(shí)現(xiàn)
了解Reader的結(jié)構(gòu)和基本使用方法后,將Reader聲明為Monad、Applicative等類的instance以獲取更抽象的表達(dá)能力。
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還有一系列的輔助函數(shù),比如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的作用是從獲取環(huán)境變量,也就是e;local是更改一個Reader的環(huán)境變量,但不會local所在的Reader中其他操作獲取到的環(huán)境變量。而asks的作用可由一個例子來展示:
ghci> runReader (asks length) “hi”
2
Reader in Haskell
Haskell中的Reader和State一樣,是通過transformer來實(shí)現(xiàn)的。ReaderT和Reader的函數(shù)簽名是:
newtype ReaderT r m a = ReaderT {
runReaderT :: r -> m a
}
type Reader e a = ReaderT e Identity a
更多的使用例子和ReaderT的實(shí)現(xiàn)細(xì)節(jié)請分別參照