有一个库提供数据类型F
和类型的函数
ffoldlIO :: (b -> a -> IO b) -> b -> F a -> IO b
功能类似
foldlIO :: (b -> a -> IO b) -> b -> [a] -> IO b
foldlIO f a = \xs -> foldr (\x r (!a') -> f a' x >>= r) return xs a
我想知道 foldlIO
(以及 ffoldlIO
)是否可以以短路方式运行。
考虑这个例子:
example1 :: IO Int
example1 = foldlIO (\a x -> if a < 4 then return (a + x) else return a) 0 [1..5]
这里foldlIO
遍历了整个列表,但是如果我们抛出一个异常来停止计算然后捕获它怎么办?像这样的事情:
data Terminate = Terminate
deriving (Show)
instance Exception Terminate
example2 :: IO Int
example2 = do
ra <- newIORef 0
let step a x
| a' < 4 = return a'
| otherwise = writeIORef ra a' >> throwIO Terminate
where a' = a + x
foldlIO step 0 [1..] `catch` \(_ :: Terminate) -> readIORef ra
这可靠吗?有没有更好的方法来终止在 IO monad(而不是其他 monad)中运行的计算,或者我根本不应该这样做?
最佳答案
例如,您可以像这样使用 ContT
monad 转换器:
example3 :: IO Int
example3 = flip runContT return . callCC $ \exit -> do
let step a x
| a' < 4 = return a'
| otherwise = exit a'
where a' = a + x
foldM step 0 [1..]
此外,您还可以定义自己的具有终止可能性的 foldM
版本。
termFoldM :: (Monad m, Foldable t) =>
((b -> ContT b m c) -> b -> a -> ContT b m b) -> b -> t a -> m b
termFoldM f a t = flip runContT return . callCC $ \exit -> foldM (f exit) a xs
example4 :: IO Int
example4 = termFoldM step 0 [1..]
where
step exit a x
| a' < 4 = return a'
| otherwise = exit a'
where a' = a + x
但是这种方式(使用ContT
)有一个问题。您无法轻松执行某些 IO
操作。例如,此代码将不会被编译,因为 step
函数必须返回 ContT Int IO Int
类型的值,而不是 IO Int
。
let step a x
| a' < 4 = putStrLn ("'a = " ++ show a') >> return a'
| otherwise = exit a'
where a' = a + x
幸运的是,您可以通过 lift
函数解决这个问题,如下所示:
let step a x
| a' < 4 = lift (putStrLn ("'a = " ++ show a')) >> return a'
| otherwise = exit a'
where a' = a + x
关于haskell - 如何终止在 `IO` monad 中运行的计算?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41122812/