Skip to content

zongwu's blog

ReaderT 相关源码分析

ReaderTHaskell里很常见的一种模式。比如读取环境配置:

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迷惑,这里的mMonad 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

ReaderTtypeclass: 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 rlocalreader 的类型也类似。

现在可以看看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

Monadreturn :: 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)

比较直观,不需要解释。

参考

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

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

  3. https://hackage.haskell.org/package/mtl-2.2.2/docs/src/Control.Monad.Reader.Class.html

  4. https://blog.ssanj.net/posts/2018-01-12-stacking-the-readert-writert-monad-transformer-stack-in-haskell.html

  5. http://cnhaskell.com/chp/18.html

  6. http://book.realworldhaskell.org/read/monad-transformers.html