为什么throw和throwIO有区别?

Ign*_*rov 5 haskell exception

我试图牢固地了解异常,以便改善条件循环的实现。为此,我正在进行各种实验,扔东西,看看有什么被发现。

这让我惊讶不已:

% cat X.hs
module Main where

import Control.Exception
import Control.Applicative

main = do
    throw (userError "I am an IO error.") <|> print "Odd error ignored."
Run Code Online (Sandbox Code Playgroud)
% ghc X.hs && ./X
...
X: user error (I am an IO error.)
Run Code Online (Sandbox Code Playgroud)
% cat Y.hs
module Main where

import Control.Exception
import Control.Applicative

main = do
    throwIO (userError "I am an IO error.") <|> print "Odd error ignored."
Run Code Online (Sandbox Code Playgroud)
% ghc Y.hs && ./Y
...
"Odd error ignored."
Run Code Online (Sandbox Code Playgroud)

我认为替代方案应该完全忽略IO错误。(不知道我是从哪里得到这个想法的,但是我当然不能提供在替代链中将被忽略的非IO异常。)因此,我认为自己可以手工制作并传递IO错误。事实证明,是否忽略它取决于包装和内容:如果我throw发生IO错误,那么某种程度上它不再是IO错误。

我完全迷路了。为什么这样工作?有意吗 这些定义深入到了GHC内部模块。虽然我可以自己或多或少地理解不同代码片段的含义,但我很难看清整个画面。

如果很难预测,甚至应该使用此Alternative实例吗?如果它取消任何同步异常,而不仅仅是以特定方式定义并以特定方式抛出的一小部分异常,会更好吗?

Li-*_*Xia 6

throwundefinedand 的概括error,它的意思是在纯代码中引发异常。当异常的值无关紧要时(通常是大多数时间),用符号?表示。为“未定义的值”。

throwIO 是引发异常的IO操作,但它本身不是未定义的值。

因此,的文档throwIO说明了区别:

throw e   `seq` x  ===> throw e
throwIO e `seq` x  ===> x
Run Code Online (Sandbox Code Playgroud)

捕获的(<|>)定义是mplusIO使用catchException,这是的严格变体catch。该严格性总结如下:

? <|> x = ?
Run Code Online (Sandbox Code Playgroud)

因此,您会xthrow变体中得到一个异常(并且永远不会运行)。

请注意,不严格地说,“未定义的动作”(即throw ... :: IO a)实际上就像是从以下角度抛出的动作catch

catch (throw   (userError "oops")) (\(e :: SomeException) -> putStrLn "caught")  -- caught
catch (throwIO (userError "oops")) (\(e :: SomeException) -> putStrLn "caught")  -- caught
catch (pure    (error     "oops")) (\(e :: SomeException) -> putStrLn "caught")  -- not caught
Run Code Online (Sandbox Code Playgroud)

  • 如果有的话,`throw`是可憎的。如果有异常,将它们抛出是一个很好的动作,那就是throwIO。 (2认同)

HTN*_*TNW 5

说你有

x :: Integer
Run Code Online (Sandbox Code Playgroud)

x当然,这意味着应该是一个整数。

x = throw _whatever
Run Code Online (Sandbox Code Playgroud)

这意味着什么?这意味着应该有一个Integer,但是只有一个错误。

现在考虑

x :: IO ()
Run Code Online (Sandbox Code Playgroud)

这意味着x应该是一个不返回任何有用值的I / O执行程序。记住,IO价值就是价值。它们是恰好代表命令性程序的值。所以现在考虑

x = throw _whatever
Run Code Online (Sandbox Code Playgroud)

那意味着那里应该有一个执行I / O的程序,但是那只是一个错误。x是不是抛出一个错误,有一个程序没有任何计划。无论您是否使用过IOErrorx都不是有效的IO程序。当您尝试执行程序时

x <|> _whatever
Run Code Online (Sandbox Code Playgroud)

您必须执行x以查看它是否引发错误。但是,您不能执行x,因为它不是程序-这是一个错误。相反,一切都会爆炸。

这与

x = throwIO _whatever
Run Code Online (Sandbox Code Playgroud)

现在x是一个有效的程序。这是一个总是会抛出错误的有效程序,但它仍然是可以实际执行的有效程序。当您尝试执行

x <|> _whatever
Run Code Online (Sandbox Code Playgroud)

现在,x将其执行,所产生的错误将被丢弃,并_whatever在其位置执行。您还可以想到计算程序/确定要执行的程序与实际执行程序之间的区别。throw在计算要执行的程序时抛出错误(这是“纯异常”),而throwIO在执行过程中抛出错误(这是“纯异常”)。这也解释了它们的类型:throw返回任何类型,因为可以“计算”所有类型,但是由于只能执行程序而throwIO受到限制IO

可以捕获执行IO程序时发生的纯异常这一事实使情况更加复杂。我相信这是设计折衷。从理论上讲,您应该捕获纯异常,因为应该总是将异常的存在指示为程序员错误,但这可能会很尴尬,因为这样您就只能处理外部错误,而程序员错误会导致所有错误。爆炸。如果我们是完美的程序员,那会很好,但事实并非如此。因此,您可以捕获纯异常。

is :: [Int]
is = []

-- fails, because the print causes a pure exception
-- it was a programmer error to call head on is without checking that it,
-- in fact, had a head in the first place
-- (the program on the left is not valid, so main is invalid)
main1 = print (head is) <|> putStrLn "Oops"
-- throws exception

-- catch creates a program that computes and executes the program print (head is)
-- and catches both impure and pure exceptions
-- the program on the left is invalid, but wrapping it with catch
-- makes it valid again
-- really, that shouldn't happen, but this behavior is useful
main2 = print (head is) `catch` (\(_ :: SomeException) -> putStrLn "Oops")
-- prints "Oops"
Run Code Online (Sandbox Code Playgroud)