来自 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
没有明显的区别。它们提供相同的功能吗?如果是,为什么类型签名中的重复?如果没有,那有什么区别?
答案 0 :(得分:12)
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
,但包围操作仍需要保持原状{{1} }。
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
和catch
,而不是限制为基数单子。可以在包lifted-base
中找到上述函数(以及许多其他函数)的通用实现。例如:
m a
编辑:现在我确实正确地重新阅读了您的问题......
不,我认为签名不需要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
,大多数monad变换器都为MonadIO
定义了一个实例但是用户可能必须为MonadIO m => MonadIO (SomeT m)
编写自己的实例。