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
通过查看示例最容易了解实际用途。例如,base 中的 bracket 具有签名
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 抛出异常,并且可以从MonadIO m 内部的IO a 捕获异常,但同样,在catch 中运行的操作和异常处理程序本身都需要为IO a不是m a。
现在MonadBaseControl IO 使得编写bracket 和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 实例。