问题出在r. 给定以下Readermonad 的定义:
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 mand的形式a。
回头看r,很明显该do块具有由and的类型String -> IO ()给出的类型。它也需要是. 现在,统一这两种类型:rp2String -> IO ()Monad m => m a
m = (->) String
a = IO ()
这通过选择匹配(->) emonad 实例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。