有一个库提供数据类型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)中运行的计算,或者我根本不应该这样做?
答案 0 :(得分:3)
例如,你可以像这样使用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
答案 1 :(得分:1)
我的第一个答案是不对的。所以,我会尽力改进。
我认为在IO monad中使用异常终止并不是一个黑客,但它看起来并不干净。我建议像这样定义实例MonadCont IO
:
data Terminate = forall a . Terminate a deriving (Typeable)
instance Show Terminate where show = const "Terminate"
instance Exception Terminate
instance MonadCont IO where
callCC f = f exit `catch` (\(Terminate x) -> return . unsafeCoerce $ x)
where exit = throwIO . Terminate
然后你可以更清洁地重写你的例子。
example :: IO Int
example = callCC $ \exit -> do
let step a x
| a' < 4 = return a'
| otherwise = exit a'
where a' = a + x
foldlIO step 0 [1..]
变体IOREf
。
data Terminate = Terminate deriving (Show, Typeable)
instance Exception Terminate
instance MonadCont IO where
callCC f = do
ref <- newIORef undefined
let exit a = writeIORef ref a >> throwIO Terminate
f exit `catch` (\Terminate -> readIORef ref)