关于Monad IO
和IO
之间的区别似乎有一些未记录的知识。注释here和here)暗示IO a
可以在否定位置使用,但可能会产生意想不到的后果:
引用Snoyman 1:
但是,我们知道一些控制流(例如异常处理) 未使用,因为它们与MonadIO不兼容。 (原因:MonadIO要求IO为正,而不是负, 位置。)例如,这使我们知道foo在 基于延续的单子,例如ContT或Conduit。
还有Kmett 2:
我倾向于导出具有MonadIO约束的函数...只要它出现 不必在否定位置采取类似IO的操作(因为 参数)。
当我的代码确实必须采取另一项单调操作作为参数时, 然后我通常必须停下来思考一下。
程序员应该了解的此类功能中是否存在危险?
例如,这是否意味着运行任意基于持续性的操作可能会重新定义控制流,从而使基于Monad IO
的界面可以安全地获得意外结果?
答案 0 :(得分:3)
程序员应该了解的此类功能中是否存在危险?
没有危险。相反,Snoyman和Kmett提出的观点是Monad IO
不允许您将IO
置于负面肯定中。
假设您要概括putStrLn :: String -> IO ()
。您可以这样做,因为IO
处于积极位置:
putStrLn' :: MonadIO m => String -> m ()
putStrLn' str = liftIO (putStrLn str)
现在,假设您要概括handle :: Exception e => (e -> IO a) -> IO a -> IO a
。您不能(至少不能仅使用MonadIO
):
handle' :: (MonadIO m, Exception e) => (e -> m a) -> m a -> m a
handle' handler act = liftIO (handle (handler . unliftIO) (unliftIO act))
unliftIO :: MonadIO m => m a -> IO a
unliftIO = error "MonadIO isn't powerful enough to make this implementable!"
您还需要更多。如果您对如何执行此操作感到好奇,请查看lifted-base
中函数的实现。例如:handle :: (MonadBaseControl IO m, Exception e) => (e -> m a) -> m a -> m a
。