我的safeMapM不会捕获任何异常

时间:2014-10-15 20:45:09

标签: exception haskell

我正在尝试创建mapM的安全版本,如果在执行时抛出异常,将从结果中排除元素。

safeMapM :: (a -> IO b) -> [a] -> IO [b]
safeMapM f []       = return []
safeMapM f (x : xs) = do
    restResult <- safeMapM f xs
    appliedResult <- onException (f x >>= evaluate . Just) (return Nothing)
    case appliedResult of
        Just x' -> return $ x' : restResult
        Nothing -> return restResult

虽然没有抓到任何东西。在一个简单的测试用例中:

safeMapM (\n -> return $ if n == 3 then error $ show n else n) [1,2,3,4,5]

它失败了:

*** Exception: 3
[1,2,"ghci>" 

为什么不被抓住? evaluate是否对error被捕获的评估不够充分?有没有办法解决这个问题,而不需要将签名更改为safeMapM :: (NFData a, NFData b) => (a -> IO b) -> [a] -> IO [b],并使用deepseq

1 个答案:

答案 0 :(得分:1)

evaluate x仅评估x足够远,无法将其WHNFJust x已经在WHNF中,即使x完全没有评估。所以你有evaluate (Just x),它根本不涉及评估x

不是将f x绑定到函数evaluate . Just,而是需要使用一个函数来评估x 将其包装在{{1}中这样的事情:

Just

使用该功能,您可以像这样重写评估行:

returnEval :: Monad m => a -> IO (m a)
returnEval x = evaluate x >>= return . return

通过该更改,表达式的结果从appliedResult <- onException (f x >>= returnEval) (return Nothing) 变为立即异常。这将我们带到下一点:[1,2,**exception**]不是一个“捕获”块,它更像是最终:“如果出现问题,运行此代码,但在任何一种情况下都会给我回到第一个表达式”。如果您将此更改为使用onException,则最终会得到您想要开始的行为:catch

[1,2,4,5]

但正如John L在评论中所说的那样,这仍然只能抓住最高级别的例外:进入WHNF所必需的例外。如果你真的想强制评估整个嵌套表达式(你可能没有),你需要一个更直率的工具,比如deepseq。