【发布时间】:2015-08-08 00:06:10
【问题描述】:
我目前正在学习 Haskell。我选择这种语言的动机之一是编写具有高度鲁棒性的软件,即完全定义的、数学上确定的、永远不会崩溃或产生错误的函数。我不是指由系统谓词(“系统内存不足”、“计算机着火”等)引起的故障,它们并不有趣,可能会使整个过程崩溃。我也不是指由无效声明引起的错误行为 (pi = 4)。
相反,我指的是由错误状态引起的错误,我想通过严格的静态类型使这些状态不可表示和不可编译(在某些函数中)来消除这些错误。在我看来,我将这些函数称为“纯”函数,并认为强类型系统可以让我完成这一任务。然而 Haskell 并没有以这种方式定义“纯”,并允许程序在任何情况下通过 error 崩溃。
Why is catching an exception non-pure, but throwing an exception is pure?
这是完全可以接受的,一点也不奇怪。然而令人失望的是,Haskell 似乎没有提供一些功能来禁止使用error 导致分支的函数定义。
下面是一个人为的例子,为什么我觉得这令人失望:
module Main where
import Data.Maybe
data Fruit = Apple | Banana | Orange Int | Peach
deriving(Show)
readFruit :: String -> Maybe Fruit
readFruit x =
case x of
"apple" -> Just Apple
"banana" -> Just Banana
"orange" -> Just (Orange 4)
"peach" -> Just Peach
_ -> Nothing
showFruit :: Fruit -> String
showFruit Apple = "An apple"
showFruit Banana = "A Banana"
showFruit (Orange x) = show x ++ " oranges"
printFruit :: Maybe Fruit -> String
printFruit x = showFruit $ fromJust x
main :: IO ()
main = do
line <- getLine
let fruit = readFruit line
putStrLn $ printFruit fruit
main
假设我偏执于纯函数readFruit 和printFruit 确实不会由于非手动状态而失败。您可以想象代码是用于发射满载宇航员的火箭,在绝对关键的例程中需要序列化和反序列化水果值。
第一个危险自然是我们在模式匹配中犯了一个错误,因为这会给我们带来无法处理的可怕错误状态。值得庆幸的是,Haskell 提供了内置的方法来防止这些,我们只需使用 -Wall 编译我们的程序,其中包括 -fwarn-incomplete-patterns 和 AHA:
src/Main.hs:17:1: Warning:
Pattern match(es) are non-exhaustive
In an equation for ‘showFruit’: Patterns not matched: Peach
我们忘记序列化 Peach fruits 并且showFruit 会抛出一个错误。这很容易解决,我们只需添加:
showFruit Peach = "A peach"
程序现在可以在没有警告的情况下编译,避免了危险!我们发射了火箭,但突然程序崩溃了:
Maybe.fromJust: Nothing
由于以下故障线路,火箭注定要坠入大海:
printFruit x = showFruit $ fromJust x
本质上,fromJust 有一个分支,它引发了一个Error,所以如果我们尝试使用它,我们甚至不希望程序编译,因为printFruit 绝对必须是“超级”纯的。我们可以解决这个问题,例如将行替换为:
printFruit x = maybe "Unknown fruit!" (\y -> showFruit y) x
我觉得奇怪的是 Haskell 决定实现严格的类型和不完整的模式检测,这一切都是为了防止无效状态被表示,但却因为没有给程序员一种检测分支的方法而落到了终点线的前面error 不允许使用。从某种意义上说,这使得 Haskell 不如 Java 健壮,后者迫使您声明允许您的函数引发的异常。
实现这一点的最简单的方法是以某种方式简单地未定义 error,通过某种形式的关联声明在本地为函数及其方程使用的任何函数。但是,这似乎是不可能的。
The wiki page about errors vs exceptions 为此通过合同提到了一个名为“扩展静态检查”的扩展,但这只会导致链接断开。
它基本上归结为:我如何让上面的程序不编译,因为它使用fromJust?欢迎所有想法、建议和解决方案。
【问题讨论】:
-
(1) 我相信these are the papers you are looking for。 (2) 注意
printFruit是不必要的。如果用户想要显示Maybe Fruit,她可以使用fmap showFruit,然后以合理的方式打开Maybe。这当然不能回答你的问题(最好让用户远离fromJust!),所以继续吧:) -
我希望 Haskell 有一种整体检查器。我什至对无异常检查器检查非穷举性和部分函数的使用感到满意,但不检查无限循环。我也想把这些警告变成错误,这样我就不能轻易忽视它们。在 GHC 中实施这可能不需要大量的工作。
-
你看过伊德里斯吗?它可能是您想要的语言。
-
另见
hlint
标签: haskell robustness