让我们看看第一种情况会发生什么:
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
您创建了 thunk ,它作为一个动作head []
被编辑。这个 thunk 不会抛出任何异常,因为它没有被评估,所以整个调用(它是 type )在没有异常的情况下产生 thunk。仅当 ghci 之后尝试打印结果时才会发生异常。如果你试过return
IO
catch (return $ head []) $ ...
IO String
String
catch (return $ head []) $ \(e :: SomeException) -> return "good message"
>> return ()
相反,不会打印任何异常。
这也是你得到 _" * Exception: Prelude.head: empty list_ 的原因。GHCi 开始打印以 . 开头的字符串"
。然后它尝试评估字符串,导致异常,并打印出.
尝试替换return
为evaluate
(将其参数强制为 WHNF)为
catch (evaluate $ head []) $ \(e :: SomeException) -> return "good message"
然后您将强制 thunk 在内部进行评估,catch
这将引发异常并让处理程序拦截它。
在另一种情况下
catch (print $ head []) $ \(e :: SomeException) -> print "good message"
当尝试检查时,异常发生在catch
零件内部,因此被处理程序捕获。print
head []
更新:正如您所建议的,一件好事是强制该值,最好是其完整的正常形式。这样,您可以确保没有“惊喜”在懒惰的重击中等着您。无论如何,这是一件好事,例如,如果您的线程返回一个未评估的 thunk 并且实际上是在另一个毫无戒心的线程中评估的,您可能会遇到难以发现的问题。
模块Control.Exception
已经有evaluate
,这会强制插入其 WHNF。我们可以轻松地对其进行扩充以使其达到完整的 NF:
import Control.DeepSeq
import Control.Seq
import Control.Exception
import Control.Monad
toNF :: (NFData a) => a -> IO a
toNF = evaluate . withStrategy rdeepseq
使用它,我们可以创建一个严格的变体catch
,强制对其 NF 执行给定的操作:
strictCatch :: (NFData a, Exception e) => IO a -> (e -> IO a) -> IO a
strictCatch = catch . (toNF =<<)
这样,我们可以确定返回的值是完全评估的,所以我们在检查它时不会得到任何异常。您可以验证如果您使用strictCatch
而不是catch
在第一个示例中,它是否按预期工作。