负位置的 IO 动作会产生意想不到的结果吗?

Can IO action in negative position give unexpected results?

关于 Monad IOIO 之间的区别,似乎有一些未记录的知识。备注 here and here) 暗示 IO a 可以用在负数位置,但可能会产生意想不到的后果:

引用 Snoyman 1:

However, we know that some control flows (such as exception handling) are not being used, since they are not compatible with MonadIO. (Reason: MonadIO requires that the IO be in positive, not negative, position.) This lets us know, for example, that foo is safe to use in a continuation-based monad like ContT or Conduit.

和克密特 2:

I tend to export functions with a MonadIO constraint... whenever it doesn't have to take an IO-like action in negative position (as an argument).

When my code does have to take another monadic action as an argument, then I usually have to stop and think about it.

程序员应该了解的此类功能是否存在危险?

例如,这是否意味着 运行 任意基于延续的操作可能会重新定义控制流,以基于 Monad IO 的接口安全的方式产生意想不到的结果?

Is there danger in such functions that programmers should know about?

没有危险。恰恰相反,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.