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

fuz*_*fuz 27 io monads haskell exception-handling

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

Rom*_*aka 24

其中一个原因是Haskell指称语义.

(纯)Haskell函数的一个简洁属性是它们的单调性 - 更明确的参数产生更多定义的值.这个属性非常重要,例如推理递归函数(阅读文章以了解原因).

根据定义,异常的表示是底部,_|_对应于给定类型的poset中的最小元素.因此,为了满足单调性要求,以下不等式需要适用于任何fHaskell函数的表示:

f(_|_) <= f(X)
Run Code Online (Sandbox Code Playgroud)

现在,如果我们可以捕获异常,我们可以通过"识别"底部(捕获异常)并返回更多定义的值来打破这种不等式:

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
Run Code Online (Sandbox Code Playgroud)

这是完整的工作演示(需要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
Run Code Online (Sandbox Code Playgroud)

正如TomMD指出的那样,另一个原因是打破了参考透明度.你可以用相同的东西取代平等的东西,得到另一个答案 (在指称意义上相同,即它们表示相同的值,而不是==意义上的.)

我们怎么做?请考虑以下表达式:

let x = x in x
Run Code Online (Sandbox Code Playgroud)

这是一个非终止递归,因此它永远不会返回任何信息,因此也表示_|_.如果我们能够捕获异常,我们可以编写函数f,如

f undefined = 0
f (let x = x in x) = _|_
Run Code Online (Sandbox Code Playgroud)

(对于严格的函数,后者总是如此,因为Haskell没有提供检测非终止计算的方法 - 原则上不能,因为Halting问题.)


Tho*_*son 14

因为异常破坏参照透明度.

您可能正在讨论实际上直接输入结果的异常.例如:

head [] = error "oh no!" -- this type of exception
head (x:xs) = x
Run Code Online (Sandbox Code Playgroud)

如果你感叹不能够捕获错误这样的话,我断言你的职能不应该依赖error或任何其他异常,但应该使用正确的返回类型(Maybe,Either,或者MonadError).这迫使您以更明确的方式处理异常情况.

与上述不同(以及导致问题背后的问题),异常可以来自诸如内存条件完全独立于计算值的信号.这显然不是一个纯粹的概念,必须存在于IO中.

  • 你能举一个以这种方式打破参照透明度的例子吗? (2认同)