使用 try 处理纯代码抛出的异常

Handling exceptions thrown by pure code with `try`

我正在研究 haskell 中的异常,偶然发现了一件我还不能理解的事情。

在 GHCi 中我这样做:

Prelude Control.Exception> let thrower = (read "A") :: Int
Prelude Control.Exception> :{
Prelude Control.Exception| let main = do
Prelude Control.Exception|     x <- (try $ return thrower) :: IO (Either SomeException Int)
Prelude Control.Exception|     print x
Prelude Control.Exception| :}
Prelude Control.Exception> main

这定义了 thrower,我的测试表达式将因异常而失败。

然后我定义 main 将表达式包装到 try 中(首先将其包装到 IO 中,因为 try 接受 IO)然后展开它来自 IO(由 try 制作)并且 print 是它。

到目前为止一切看起来都很棒 - 在 repl 中评估 main 会返回包含在 Either:

中的异常
Right *** Exception: Prelude.read: no parse

但是,如果我尝试编译并执行与应用程序相同的代码:

module Main where

import Control.Exception

thrower = (read "A") :: Int

main = do
    x <- (try $ return thrower) :: IO (Either SomeException Int)
    print x

...它因异常而崩溃:

haskelltest.exe: Prelude.read: no parse

似乎例外情况已经过去 try

我在这里遗漏了什么,正确的处理方法是什么?

嗯,基本上 (as Sebastian Redl pointed out earlier) 这是一个严格性问题。 return thrower 不会以任何方式评估 thrower,因此 try 成功 。只有当 Either SomeException Int 的内容被打印出来时,即 Right throwerread 才真正尝试解析 "A",但失败了……但是此时,try已经结束了。

防止这种情况的方法是 inject the parse result strictly 进入 IO monad,

main = do
    x <- try $ evaluate thrower :: IO (Either SomeException Int)
    print x

我不知道为什么 try 在 GHCi 中使用您的代码失败;我敢说不应该。 啊哈:它实际上并没有失败

可以说,这是为什么在 Haskell 中通常应该避免异常的一个例子。 Use a suitable monad transformer 明确可能发生的错误,并获得对错误检查的可靠评估。

您缺少的部分内容是,当您 运行 在 ghci 中编写代码时,try 也没有捕捉到 read "A" :: Int 引发的错误。您不期待 Left <something> 结果吗?请参阅 leftaroundabout 的回答,了解为什么会这样。

这里 ghci 和 ghc 之间的区别可能是由于输出缓冲:输出到 stdout(如这里的 "Right ")在 ghci 中是无缓冲的,但在编译程序中默认行缓冲。