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