问题出在r
. 给定以下Reader
monad 的定义:
instance Monad ((->) e) where
return = const
f >>= g = \x -> g (f x) x
我们可以简化r
:
r = p1 >> p2
= (>>=) p1 (\_ -> p2)
= (\f g x -> g (f x) x) p1 (\_ -> p2)
= \x -> (\_ -> p2) (p1 x) x
= \x -> p2 x
这也表明Reader
's(>>)
只是const
一个更具体的类型。
如果要分发环境,然后执行这两个动作,则必须将应用的结果绑定p1
到环境中,例如:
r = do a1 <- p1
a2 <- p2
return (a1 >> a2)
或使用Applicative
:
r = (>>) <$> p1 <*> p2
扩展Reader
部分,Control.Monad.Reader
提供了Reader
.
- 隐含的
(->) e
,这是函数r
使用的
- monad 转换
ReaderT e m
器,一个新的类型的函数包装器e -> m a
- 显式
Reader e
, 定义ReaderT
为ReaderT e Identity
没有任何进一步的信息,将使用隐式(->) e
。为什么?
块的整体类型do
由最后一个表达式给出,它也被限制Monad m => m a
为 some m
and的形式a
。
回头看r
,很明显该do
块具有由and的类型String -> IO ()
给出的类型。它也需要是. 现在,统一这两种类型:r
p2
String -> IO ()
Monad m => m a
m = (->) String
a = IO ()
这通过选择匹配(->) e
monad 实例e = String
。
作为 monad 转换器,ReaderT
负责内部管道以确保内部 monad 的操作正确排序和执行。要选择ReaderT
,必须明确提及它(通常在类型签名中,但将类型固定为 的函数ReaderT
,例如runReaderT
,也可以使用):
r :: ReaderT String IO ()
r = do ? p1
? p2
r' :: String -> IO ()
r' = runReaderT r
这带来了另一个问题,p1
并且p2
有一个String -> IO ()
与 required 不匹配的type ReaderT String IO ()
。
临时解决方案(专为这种情况量身定制)只是应用
ReaderT :: (e -> m a) -> ReaderT e m a
为了获得更通用的东西,MonadIO
类型类可以将IO
动作提升到转换器中,并且MonadReader
类型类允许访问环境。只要变压器堆栈中的某个位置IO
(或分别)存在,这两种类型的类就可以工作。ReaderT
lift' :: (MonadIO m, MonadReader a m) => (a -> IO b) -> m b
lift' f = do
env <- ask -- get environment
let io = f env -- apply f to get the IO action
liftIO io -- lift IO action into transformer stack
或更简洁地说:
lift' f = ask >>= liftIO . f
关于您在评论中的问题,您可以通过这种方式实现相关实例:
newtype ReaderT e m a = ReaderT { runReaderT :: e -> m a }
instance Monad m => Monad (ReaderT e m) where
return = ReaderT . const . return
-- The transformers package defines it as "lift . return".
-- These two definitions are equivalent, though.
m >>= f = ReaderT $ \e -> do
a <- runReaderT m e
runReaderT (f a) e
instance Monad m => MonadReader e (ReaderT e m) where
ask = ReaderT return
local f m = ReaderT $ runReaderT m . f
reader f = ReaderT (return . f)
实际的类型类可以在mtl
包中找到(包,类型类),包中的新类型和Monad
实例transformers
(包,类型类)。
至于做一个e -> m a
Monad
实例,你不走运。Monad
需要 kind 的类型构造函数* -> *
,这意味着我们正在尝试做这样的事情(在伪代码中):
instance Monad m => Monad (/\a -> e -> m a) where
-- ...
其中/\
代表类型级 lambda。然而,最接近类型级别 lambda 的东西是类型同义词(在我们可以创建类型类实例之前必须完全应用它,所以这里没有运气)或类型族(不能用作类型的参数类)。使用类似的东西再次(->) e . m
导致newtype
。