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 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.