来自network-conduit的函数runTCPClient具有以下签名:
runTCPClient :: (MonadIO m, MonadBaseControl IO m) => ClientSettings m -> Application m -> m ()
MonadIO m
提供
liftIO :: IO a -> m a
并MonadBaseControl IO m
提供
liftBase :: IO a -> m a
没有明显的区别.它们提供相同的功能吗?如果是,为什么类型签名中的重复?如果没有,有什么区别?
liftBase
MonadBase
其中一部分是MonadIO
对任何基础monad 的推广,正如你所说,MonadBase IO
它提供了相同的功能MonadIO
.
但是,MonadBaseControl
野兽有点复杂.在MonadBaseControl IO m
你有
liftBaseWith :: ((forall a. m a -> IO (StM m a)) -> IO a) -> m a restoreM :: StM m a -> m a
通过查看示例,最容易看到实际用途.例如,bracket
from base
具有签名
bracket :: IO a -> (a -> IO b) -> (a -> IO c) -> IO c
只需MonadBase IO m
(或MonadIO m
)您可以将主要bracket
调用解除,m
但包围操作仍然需要保持原状IO
.
throw
并且catch
是甚至更好的例子:
throw :: Exception e => e -> a catch :: Exception e => IO a -> (e -> IO a) -> IO a
您可以轻松地从任何内容抛出异常MonadIO m
,您可以从IO a
内部捕获异常,MonadIO m
但同样,运行的操作catch
和异常处理程序本身IO a
都不需要m a
.
现在MonadBaseControl IO
可以以允许参数动作也是类型的方式进行编写bracket
,而不是限制为基本monad.可以在包中找到上述函数(以及许多其他函数)的通用实现.例如:catch
m a
lifted-base
catch :: (MonadBaseControl IO m, Exception e) => m a -> (e -> m a) -> m a bracket :: MonadBaseControl IO m => m a -> (a -> m b) -> (a -> m c) -> m c
编辑:现在我实际上正确地重新阅读了你的问题......
不,我没有看到任何理由为什么签名需要两者MonadIO m
,MonadBaseControl IO m
因为MonadBaseControl IO m
应该暗示MonadBase IO m
哪些能够实现完全相同的功能.所以也许它只是一些旧版本的遗留物.
看看来源,可能只是因为runTCPClient
电话sourceSocket
和sinkSocket
内部以及那些要求MonadIO
.我猜测包中的所有函数不能简单使用MonadBase IO
的原因MonadIO
是人们更熟悉,大多数monad变换器都有一个实例定义,MonadIO m => MonadIO (SomeT m)
但是用户可能必须编写自己的实例MonadBase IO
.