我正在尝试创建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
?
答案 0 :(得分:1)
evaluate x
仅评估x
足够远,无法将其WHNF,Just 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。