为什么Haskell异常只能在IO monad中捕获?

时间:2010-09-04 15:07:33

标签: haskell exception-handling io monads

有人可以解释为什么异常可能会被抛出IO monad,但可能只会被捕获到它内部吗?

3 个答案:

答案 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部分?