我试图牢固地了解异常,以便改善条件循环的实现。为此,我正在进行各种实验,扔东西,看看有什么被发现。
这让我惊讶不已:
% 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实例吗?如果它取消任何同步异常,而不仅仅是以特定方式定义并以特定方式抛出的一小部分异常,会更好吗?
throw是undefinedand 的概括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)
因此,您会x在throw变体中得到一个异常(并且永远不会运行)。
请注意,不严格地说,“未定义的动作”(即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)
说你有
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是不是抛出一个错误,有一个程序是没有任何计划。无论您是否使用过IOError,x都不是有效的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)