为什么 throw 和 throwIO 有区别?
Why is there difference between throw and throwIO?
我正在努力牢牢掌握异常情况,以便提高我的 conditional loop implementation。为此,我正在进行各种实验,扔东西,看看能抓到什么。
这个让我惊喜不已:
% 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."
% ghc X.hs && ./X
...
X: user error (I am an IO error.)
% 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."
% ghc Y.hs && ./Y
...
"Odd error ignored."
我认为 Alternative 应该完全忽略 IO 错误。 (不确定我从哪里得到这个想法,但我当然不能提供在替代链中会被忽略的非 IO 异常。) 所以我想我可以手工制作和交付IO 错误。事实证明,它是否被忽略取决于包装和内容:如果我 throw
一个 IO 错误,它在某种程度上 不再是 一个 IO 错误。
我完全迷路了。为什么会这样?是故意的吗?这些定义深入到 GHC 内部模块;虽然我自己或多或少可以理解不同的代码片段的含义,但我很难看到全貌。
如果很难预测,是否应该使用这个替代实例?如果它使任何同步异常静音,而不只是以特定方式 定义 和 抛出 的一小部分异常,这不是更好吗?具体方法?
此答案的其余部分可能不完全正确。但从根本上说,区别在于:throwIO
终止并且 return 是一个 IO
操作,而 throw
不 终止。
一旦您尝试计算 throw (userError "...")
,您的程序就会中止。 <|>
从来没有机会查看它的第一个参数来决定是否应该评估第二个参数;事实上,它从来没有 得到 第一个参数,因为 throw
没有 return 一个值。
对于 throwIO
,<|>
没有评估任何东西;它正在创建一个新的 IO
动作,当它 does 被执行时,将首先查看它的第一个参数。运行时可以 "safely" 执行 IO
操作并看到它实际上没有提供值,此时它可以停止并尝试 [=14] 的另一个 "half" =] 表达式.
throw
是 undefined
和 error
的概括,它的意思是在纯代码中抛出异常。当异常的值无关紧要时(大多数情况下),它用符号 ⟘ 表示 "undefined value".
throwIO
是一个抛出异常的 IO 操作,但它本身不是一个未定义的值。
throwIO
的文档因此说明了差异:
throw e `seq` x ===> throw e
throwIO e `seq` x ===> x
要注意的是 (<|>)
被定义为 mplusIO
which uses catchException
,这是 catch
的严格变体。严格程度总结如下:
⟘ <|> x = ⟘
因此在 throw
变体中出现异常(并且 x
永远不会 运行)。
请注意,不严格地说,"undefined action"(即 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
假设你有
x :: Integer
这意味着x
当然应该是一个整数。
x = throw _whatever
这是什么意思?这意味着应该有一个 Integer
,但实际上只是一个错误。
现在考虑
x :: IO ()
这意味着x
应该是一个I/O-performing程序,returns没有有用的价值。请记住,IO
值只是 值 。它们是恰好代表命令式程序的值。所以现在考虑
x = throw _whatever
这意味着那里应该有一个 I/O-performing 程序,但实际上只是一个错误。 x
不是一个会抛出错误的程序—— 是 没有程序。无论您是否使用过 IOError
,x
都不是有效的 IO
程序。当您尝试执行程序时
x <|> _whatever
你必须执行x
看它是否抛出错误。但是,你 不能 执行 x
,因为它不是程序——这是一个错误。相反,一切都爆炸了。
这与
有很大不同
x = throwIO _whatever
现在 x
是一个有效的程序。这是一个总是碰巧抛出错误的有效程序,但它仍然是一个可以实际执行的有效程序。当你尝试执行
x <|> _whatever
现在,x
被执行,产生的错误被丢弃,_whatever
被执行。您还可以认为计算 program/figuring 执行的内容与实际执行它之间存在差异。 throw
在计算要执行的程序时抛出错误("pure exception"),而 throwIO
在执行期间抛出错误("impure exception")。这也解释了它们的类型:throw
returns任何类型因为所有类型都可以"computed",但是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"
我正在努力牢牢掌握异常情况,以便提高我的 conditional loop implementation。为此,我正在进行各种实验,扔东西,看看能抓到什么。
这个让我惊喜不已:
% 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."
% ghc X.hs && ./X
...
X: user error (I am an IO error.)
% 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."
% ghc Y.hs && ./Y
...
"Odd error ignored."
我认为 Alternative 应该完全忽略 IO 错误。 (不确定我从哪里得到这个想法,但我当然不能提供在替代链中会被忽略的非 IO 异常。) 所以我想我可以手工制作和交付IO 错误。事实证明,它是否被忽略取决于包装和内容:如果我 throw
一个 IO 错误,它在某种程度上 不再是 一个 IO 错误。
我完全迷路了。为什么会这样?是故意的吗?这些定义深入到 GHC 内部模块;虽然我自己或多或少可以理解不同的代码片段的含义,但我很难看到全貌。
如果很难预测,是否应该使用这个替代实例?如果它使任何同步异常静音,而不只是以特定方式 定义 和 抛出 的一小部分异常,这不是更好吗?具体方法?
此答案的其余部分可能不完全正确。但从根本上说,区别在于:throwIO
终止并且 return 是一个 IO
操作,而 throw
不 终止。
一旦您尝试计算 throw (userError "...")
,您的程序就会中止。 <|>
从来没有机会查看它的第一个参数来决定是否应该评估第二个参数;事实上,它从来没有 得到 第一个参数,因为 throw
没有 return 一个值。
对于 throwIO
,<|>
没有评估任何东西;它正在创建一个新的 IO
动作,当它 does 被执行时,将首先查看它的第一个参数。运行时可以 "safely" 执行 IO
操作并看到它实际上没有提供值,此时它可以停止并尝试 [=14] 的另一个 "half" =] 表达式.
throw
是 undefined
和 error
的概括,它的意思是在纯代码中抛出异常。当异常的值无关紧要时(大多数情况下),它用符号 ⟘ 表示 "undefined value".
throwIO
是一个抛出异常的 IO 操作,但它本身不是一个未定义的值。
throwIO
的文档因此说明了差异:
throw e `seq` x ===> throw e
throwIO e `seq` x ===> x
要注意的是 (<|>)
被定义为 mplusIO
which uses catchException
,这是 catch
的严格变体。严格程度总结如下:
⟘ <|> x = ⟘
因此在 throw
变体中出现异常(并且 x
永远不会 运行)。
请注意,不严格地说,"undefined action"(即 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
假设你有
x :: Integer
这意味着x
当然应该是一个整数。
x = throw _whatever
这是什么意思?这意味着应该有一个 Integer
,但实际上只是一个错误。
现在考虑
x :: IO ()
这意味着x
应该是一个I/O-performing程序,returns没有有用的价值。请记住,IO
值只是 值 。它们是恰好代表命令式程序的值。所以现在考虑
x = throw _whatever
这意味着那里应该有一个 I/O-performing 程序,但实际上只是一个错误。 x
不是一个会抛出错误的程序—— 是 没有程序。无论您是否使用过 IOError
,x
都不是有效的 IO
程序。当您尝试执行程序时
x <|> _whatever
你必须执行x
看它是否抛出错误。但是,你 不能 执行 x
,因为它不是程序——这是一个错误。相反,一切都爆炸了。
这与
有很大不同x = throwIO _whatever
现在 x
是一个有效的程序。这是一个总是碰巧抛出错误的有效程序,但它仍然是一个可以实际执行的有效程序。当你尝试执行
x <|> _whatever
现在,x
被执行,产生的错误被丢弃,_whatever
被执行。您还可以认为计算 program/figuring 执行的内容与实际执行它之间存在差异。 throw
在计算要执行的程序时抛出错误("pure exception"),而 throwIO
在执行期间抛出错误("impure exception")。这也解释了它们的类型:throw
returns任何类型因为所有类型都可以"computed",但是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"