10

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

没有明显的区别。它们提供相同的功能吗?如果是,为什么类型签名中有重复?如果不是,有什么区别?

4

1 回答 1

12

liftBaseMonadBase是任何基本单子的概括的一部分,MonadIO并且正如您所说,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

通过查看示例最容易了解实际用途。例如,bracketfrombase有签名

bracket ::  IO a -> (a -> IO b) -> (a -> IO c) -> IO c

只需MonadBase IO m(或MonadIO m),您就可以将主要bracket调用提升到m,但括号操作仍然需要在普通的 oldIO中。

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,并且catch允许参数操作也具有类型m a,而不是仅限于基本 monad。上述功能(以及许多其他功能)的通用实现可以在包中找到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 mMonadBaseControl IO m因为MonadBaseControl IO m应该暗示MonadBase IO m它启用了完全相同的功能。所以也许它只是一些旧版本的遗留物。

查看源代码,可能只是因为runTCPClient调用sourceSocketsinkSocket内部以及那些需要MonadIO. 我猜测包中的所有函数不简单使用MonadBase IO的原因MonadIO是人们更熟悉,并且大多数 monad 转换器都定义了一个实例,MonadIO m => MonadIO (SomeT m)但用户可能必须为MonadBase IO.

于 2014-01-05T09:12:11.573 回答