如何终止在`IO` monad中运行的计算?

时间:2016-12-13 13:51:10

标签: haskell exception io monads short-circuiting

有一个库提供数据类型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)中运行的计算,或者我根本不应该这样做?

2 个答案:

答案 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)