ReaderT 相关源码分析
ReaderT是Haskell里很常见的一种模式。比如读取环境配置:
import Control.Monad.Trans.Reader
import qualified Data.Map.Lazy as Map
import Text.Read (readMaybe)
import Text.Printf (printf)
type Config = Map.Map String String
envConfig :: Config
envConfig = Map.fromList [("host", "localhost"), ("port", "7654")]
getHost :: Monad m => ReaderT Config m (Maybe String)
getHost = do
config <- ask
return (Map.lookup "host" config)
getPort :: Monad m => ReaderT Config m (Maybe Int)
getPort = do
config <- ask
return (Map.lookup "port" config >>= readMaybe)
localModify :: Monad m => ReaderT Config m (Maybe String)
localModify = local(\c -> Map.insert "host" "127.0.0.1" c) getHost
main :: IO ()
main = do
-- 读取环境配置信息
hostM <- runReaderT getHost envConfig
portM <- runReaderT getPort envConfig
let host = maybe "-" id hostM
let port = maybe "-" show portM
putStrLn (printf "host: %s" host)
putStrLn (printf "port: %s" port)
-- 临时修改环境配置
hostM' <- runReaderT localModify envConfig
let host' = maybe "-" id hostM'
putStrLn (printf "local host: %s" host')
-- 读取环境配置信息
hostM'' <- runReaderT getHost envConfig
let host'' = maybe "-" id hostM''
putStrLn (printf "host: %s" host'')
执行的结果:
host: localhost
port: 7654
local host: 127.0.0.1
host: localhost
从源码层面分析一下ReaderT。
ReaderT 定义
在Control.Monad.Trans.Reader.class 模块里:
-- | The reader monad transformer,
-- which adds a read-only environment to the given monad.
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
instance (Functor m) => Functor (ReaderT r m) where
...
instance (Applicative m) => Applicative (ReaderT r m) where
...
instance (Monad m) => Monad (ReaderT r m) where
return = lift . return
m >>= k = ReaderT $ \ r -> do
a <- runReaderT m r
runReaderT (k a) r
看定义,可以直观认为ReaderT r m a 就是封装了r -> m a函数。
重点来看一看 Monad (ReaderT r m) 的实现。
关于 return = lift . return: 等号右边的lift定义实际上来自于 import Control.Monad.Trans.Class
class MonadTrans t where
-- | Lift a computation from the argument monad to the constructed monad.
lift :: (Monad m) => m a -> t m a
对于等号右边的retrun就是Monad m 基本的定义 retrun:: m a 刚好是 m a类型。
于是可以得到Monad (ReaderT r m) 的return:: ReaderT r m a,注意这里要有意识地把 (ReaderT r m)当作Monad m里的m 。
关于m >>= k 的定义就有点晦涩。理解的关键点是不要被符号m迷惑,这里的m 是Monad m吗?
当然不是。回忆一下简单版Monad m关于>>= 的定义:
class Monad m where
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
return :: a -> m a
>>=的两个参数的类型分别是m a 和(a -> m b) ,结果类型是m b。
刚才说过把 (ReaderT r m)当作Monad m里的m 。于是Monad (ReaderT r m) 的>>=定义实质上是:
(>>=) :: ReaderT r m a -> (a -> ReaderT r m b) -> ReaderT r m b
我们把晦涩的m,k用其他符号替换掉,
g >>= h = ReaderT $ \r -> do
a <- runReaderT g r
runReaderT (h a) r
我们知道 runReaderT::ReaderT r m a ->r -> m a ,同时g:: ReaderT r m a,那么 runReaderT g r:: m a。<-会取出a 。
h::a -> ReaderT r m b ,那么 runReaderT (h a) r::m b。
于是得到了 ReaderT $ \r -> m b这刚好又是ReaderT 的构造函数最终得到ReaderT r m b。
Reader
看完ReaderT就可以看看Reader类型了( 在同一个文件内定义)。
-- | The parameterizable reader monad.
--
-- Computations are functions of a shared environment.
--
-- The 'return' function ignores the environment, while @>>=@ passes
-- the inherited environment to both subcomputations.
type Reader r = ReaderT r Identity
type关键字定义别名,类型与别名可以通用。 Reader r只是把ReaderT r m 类型的m替换为Identity。
于是可以认为Reader r封装了r -> a函数。
用于ReaderT r m的方法,都适用于Reader r。
一些常用的方法:
-- | Constructor for computations in the reader monad (equivalent to 'asks').
reader :: (Monad m) => (r -> a) -> ReaderT r m a
reader f = ReaderT (return . f)
-- | Runs a @Reader@ and extracts the final value from it.
-- (The inverse of 'reader'.)
runReader
:: Reader r a -- ^ A @Reader@ to run.
-> r -- ^ An initial environment.
-> a
runReader m = runIdentity . runReaderT m
-- | Transform the value returned by a @Reader@.
--
-- * @'runReader' ('mapReader' f m) = f . 'runReader' m@
mapReader :: (a -> b) -> Reader r a -> Reader r b
mapReader f = mapReaderT (Identity . f . runIdentity)
{-# INLINE mapReader #-}
-- | Execute a computation in a modified environment
-- (a specialization of 'withReaderT').
--
-- * @'runReader' ('withReader' f m) = 'runReader' m . f@
withReader
:: (r' -> r) -- ^ The function to modify the environment.
-> Reader r a -- ^ Computation to run in the modified environment.
-> Reader r' a
withReader = withReaderT
ReaderT的typeclass: MonadReader
在Control.Monad.Reader.Class模块里定义了:
instance Monad m => MonadReader r (ReaderT r m) where
ask = ReaderT.ask
local = ReaderT.local
reader = ReaderT.reader
先看MonadReader 的定义
class Monad m => MonadReader r m | m -> r where
ask :: m r
ask = reader id
-- | Executes a computation in a modified environment.
local :: (r -> r) -- ^ The function to modify the environment.
-> m a -- ^ @Reader@ to run in the modified environment.
-> m a
-- | Retrieves a function of the current environment.
reader :: (r -> a) -- ^ The selector function to apply to the environment.
-> m a
reader f = do
r <- ask
return (f r)
-- | Retrieves a function of the current environment.
asks :: MonadReader r m
=> (r -> a) -- ^ The selector function to apply to the environment.
-> m a
asks = reader
上面的定义中,ask reader两个方法Mutual recursion 相互递归定义。重点看reader
reader f = do
r <- ask
return (f r)
改成去掉do的写法:
reader f = ask >>= \r -> return(f r)
从上面的声明可以知道,ask:: m a,这里注意MonadReader r m不是Monad,只有Monad m。
那么>>=是Monad m的方法。
-- ask:: m a
-- \r -> retrun(f r) :: r -> m a
ask >>= \r -> return(f r)
刚好得到reader f :: m a。
回到instance的定义:
import Control.Monad.Trans.Reader (ReaderT)
import qualified Control.Monad.Trans.Reader as ReaderT (ask, local, reader)
...
instance Monad m => MonadReader r (ReaderT r m) where
ask = ReaderT.ask
local = ReaderT.local
reader = ReaderT.reader
从MonadReader r m的定义里,我们知道ask:: m r,依据上面instance的定义,(ReaderT r m就是MonadReader r m中的m)那么 ask:: ReaderT r m r。local,reader 的类型也类似。
现在可以看看ReaderT里对应的方法的定义。
-- | Fetch the value of the environment.
ask :: (Monad m) => ReaderT r m r
ask = ReaderT return
-- | Execute a computation in a modified environment
-- (a specialization of 'withReaderT').
--
-- * @'runReaderT' ('local' f m) = 'runReaderT' m . f@
local
:: (r -> r) -- ^ The function to modify the environment.
-> ReaderT r m a -- ^ Computation to run in the modified environment.
-> ReaderT r m a
local = withReaderT
-- | Retrieve a function of the current environment.
--
-- * @'asks' f = 'liftM' f 'ask'@
asks :: (Monad m)
=> (r -> a) -- ^ The selector function to apply to the environment.
-> ReaderT r m a
asks f = ReaderT (return . f)
ask
ask的注释解释了,该函数用于获取环境的值。(实际上就是 r) ,它的实现:
ask = ReaderT return
有点迷糊,ReaderT的类型是什么?
这里的ReaderT显然是值构造子(value Constructor,有时候也叫 data constructor),给它一个函数可以构造一个 ReaderT r m a。可以得知:
ReaderT :: (r -> m a) -> ReaderT r m a
return是哪个模块定义的return? 是刚刚分析的Monad (ReaderT r m) 的return么?不是。
文件开头import的
import Control.Monad
是Monad的return :: a -> m a
把ReaderT return 放在一起分析:
((r -> m a) -> ReaderT r m a) (a -> m a)
第一个函数r -> m a 需要的是r , 我们知道a可以是任何类型,所以return的类型还可以写做:return:: r -> m r
于是ReaderT return :: ReaderT r m r。
local
local = withReaderT,看看withReaderT的定义:
-- | Execute a computation in a modified environment
-- (a more general version of 'local').
--
-- * @'runReaderT' ('withReaderT' f m) = 'runReaderT' m . f@
withReaderT
:: (r' -> r) -- ^ The function to modify the environment.
-> ReaderT r m a -- ^ Computation to run in the modified environment.
-> ReaderT r' m a
withReaderT f m = ReaderT $ runReaderT m . f
这里只需要注意withReaderT f m 里的m::ReaderT r m a。
asks
-- | Retrieve a function of the current environment.
--
-- * @'asks' f = 'liftM' f 'ask'@
asks :: (Monad m)
=> (r -> a) -- ^ The selector function to apply to the environment.
-> ReaderT r m a
asks f = ReaderT (return . f)
比较直观,不需要解释。
参考
-
https://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Reader.html
-
https://hackage.haskell.org/package/transformers-0.6.0.2/docs/src/Control.Monad.Trans.Reader.html
-
https://hackage.haskell.org/package/mtl-2.2.2/docs/src/Control.Monad.Reader.Class.html
-
http://book.realworldhaskell.org/read/monad-transformers.html