undefined
或 error
以阻止程序运行。否则,我认为使用异常编程是一个逻辑设计缺陷。但是 Haskell 有一个高级异常模块 Control.Exception
,它被 Prelude
使用。我读到 C++ 中使用异常的原因是为了防止代码中出现大量的“调用函数,然后检查状态”行。但这些东西可以在 Haskell 中抽象出来。我能看到的 Haskell 中处理异常的另一个原因只能是 FFI 中处理外部异常,但仅适用于 Haskell 函数内部包装调用的情况。undefined
或 error
以阻止程序运行。否则,我认为使用异常编程是一个逻辑设计缺陷。但是 Haskell 有一个高级异常模块 Control.Exception
,它被 Prelude
使用。我读到 C++ 中使用异常的原因是为了防止代码中出现大量的“调用函数,然后检查状态”行。但这些东西可以在 Haskell 中抽象出来。我能看到的 Haskell 中处理异常的另一个原因只能是 FFI 中处理外部异常,但仅适用于 Haskell 函数内部包装调用的情况。依我之见,异常意味着“您违反了函数的契约”。 我所说的并不是类型合同,而是通常在注释中找到的内容。
-- The reciprocal is not defined for the value 0
myRecip :: Fractional a => a -> a
myRecip x | x == 0 = throw DivideByZero
| otherwise = 1 / x
当然,您始终可以以“安全”的方式提供这些函数:
safeRecip :: Fractional a => a -> Maybe a
safeRecip x | x == 0 = Nothing
| otherwise = Just $ 1 / x
或许我们甚至应该将这个模式抽象化
restrictInput :: (a -> Bool) -> (a -> b) -> (a -> Maybe b)
restrictInput pred f x = if pred x then Just (f x) else Nothing
safeRecip' = restrictInput (/= 0) myRecip
你可以想象类似的组合器来使用Either
而不是Maybe
来报告失败。既然我们有能力使用纯函数处理这些事情,为什么还要使用不纯的异常模型呢?好吧,大多数Haskeller会告诉你坚持纯度。但真相是,添加额外的层级很麻烦。你不能再写
prop_recip x = x == (recip . recip) x
因为现在recip x
的结果是一个Maybe
。你可以做一些有用的事情,使用 (a -> a)
函数来做你以前无法做的事情。现在你必须考虑组合Maybes
。如果你熟悉单子,这当然是微不足道的:prop_recip 0 = (safeRecip >=> safeRecip) 0 == Nothing
prop_recip x = (safeRecip >=> safeRecip) x == Just x
但问题在于这里。新手在单子组合方面通常几乎什么都不知道。正如许多#haskell irc频道上的人会迅速告诉您的那样,Haskell委员会在语言设计方面做出了一些相当奇怪的决定,以迎合新手。我们希望能够说“您无需了解单子即可开始在Haskell中制作有用的东西”。我基本上同意那种看法。
tl;dr 对问题“What is an exception?”进行快速回答:
还可能有其他解释。
另请参见Haskell Report > Basic Input/Output > Exception Handling in the IO Monad
*我实际上在#haskell irc上询问了他们是否赞成这个说法。我唯一得到的回复是"wonky" :)
,所以很明显通过反对证明了这个说法是正确的。
[编辑]请注意,error
和undefined
以throw
为基础定义:
error :: [Char] -> a
error s = throw (ErrorCall s)
undefined :: a
undefined = error "Prelude.undefined"
error
并不是什么神奇的事情:你可以捕获抛出的ErrorCall
异常并在不引起骚动的情况下处理它。 let foo a = error "rawr" in Control.Exception.Base.catch (foo ()) (\(ErrorCall msg) -> putStrLn msg)
会打印出消息"rawr"。 - Dan Burtonerror
本质上无非是(throw . ErrorCall)
这个宏,所以我不同意使用除了'error'之外的异常是错误的。只要你不捕获异常,你可以用任何老旧的异常来终止程序;ErrorCall
并没有什么不同。然而,关于ErrorCall
(以及它的便利函数error
)的好处在于,它使得你可以简单地将错误消息写成字符串,而不需要费事地创建自己的异常类型。 - Dan BurtonControl.Exception
呢?Maybe
和Either
之类的东西以纯粹的方式进行处理。(例如,如果你要反转矩阵,那么矩阵可能是不可逆的,所以你最好返回一个Maybe Matrix
作为结果。)对于你无法合理预见的事情(例如,一些其他程序刚刚删除了你正在处理的文件),异常是唯一的选择。Control.Exception
不仅包含用于定义各种不同类型的东西,还包含大量用于处理异常的内容。我不在乎我调用的代码是否执行了错误操作,因此存在Bug;我仍然希望能够告诉我刚才通信的客户端连接即将关闭,记录一些描述信息到某个日志文件,并进行其他清理工作,而不是让我的程序突然停止。"异常是一种合法的流程控制形式。对我来说不清楚的是,当程序员拥有一个工具时,为什么会坚持认为它“只适用于”某些情况并排除其他可能的用途。
例如,如果您正在执行回溯计算,可以使用异常进行回溯。在Haskell中,使用列表单子可能更常见,但异常也是一种合法的方法。
IO a -> a
?”)相比,这个问题明显增加了价值。 - fuz