有人可以解释为什么异常可能会被抛出IO monad,但可能只会被捕获到它内部吗?
答案 0 :(得分:24)
其中一个原因是denotational semantics of Haskell。
(纯)Haskell函数的一个简洁属性是它们的单调性 - 更明确的参数产生更多定义的值。这个属性非常重要,例如推理递归函数(阅读文章以了解原因)。
根据定义表示异常是底部_|_
,它是与给定类型对应的poset中的最小元素。因此,为了满足单调性要求,以下不等式需要适用于Haskell函数的任何表示f
:
f(_|_) <= f(X)
现在,如果我们能够捕获异常,我们可以通过“识别”底部(捕获异常)并返回更多定义的值来打破这种不平等:
f x = case catch (seq x True) (\exception -> False) of
True -> -- there was no exception
undefined
False -> -- there was an exception, return defined value
42
这是完整的工作演示(需要base-4 Control.Exception):
import Prelude hiding (catch)
import System.IO.Unsafe (unsafePerformIO)
import qualified Control.Exception as E
catch :: a -> (E.SomeException -> a) -> a
catch x h = unsafePerformIO $ E.catch (return $! x) (return . h)
f x = case catch (seq x True) (\exception -> False) of
True -> -- there was no exception
undefined
False -> -- there was an exception, return defined value
42
正如TomMD指出的那样,另一个原因是打破参考透明度。你可以用相同的东西取代平等的东西,得到另一个答案(在指称意义上相等,即它们表示相同的值,而不是==
意义上的。)
我们怎么做?请考虑以下表达式:
let x = x in x
这是一个非终止递归,因此它永远不会返回任何信息,因此也由_|_
表示。如果我们能够捕获异常,我们可以编写函数f,例如
f undefined = 0
f (let x = x in x) = _|_
(对于严格的函数,后者总是如此,因为Haskell没有提供检测非终止计算的方法 - 原则上不能,因为Halting problem。)
答案 1 :(得分:14)
因为例外可以中断referential transparency。
您可能正在讨论实际上直接输入结果的异常。例如:
head [] = error "oh no!" -- this type of exception
head (x:xs) = x
如果你感到遗憾的是无法捕捉到这样的错误,那么我告诉你,这些函数不应该依赖于error
或任何其他异常,而应该使用正确的返回类型({{1 }},Maybe
或者MonadError)。这迫使你以更明确的方式处理异常情况。
与上述不同(以及导致问题背后的问题的原因),异常可以来自诸如内存条件完全独立于计算值的信号。这显然不是一个纯粹的概念,必须存在于IO中。
答案 2 :(得分:2)
我的解释可能有问题,但这就是我理解的方式。
由于函数在Haskell中是纯粹的,因此编译器有权按照他希望的任何顺序对它们进行求值,并且它仍然会产生相同的结果。例如,给定函数:
square :: Int -> Int
square x = x * x
表达式square (square 2)
可以用不同的方式进行评估,但它总是减少到16的相同结果。
如果我们从其他地方拨打square
:
test x = if x == 2
then square x
else 0
square x
可以在以后评估,&#34;在&#34;之后实际需要值时的test
函数。在那一刻,调用堆栈可能与您在Java中所期望的完全不同。
所以,即使我们想抓住square
引发的潜在异常,您应该在哪里放置catch
部分?