我目前正在学习Haskell。我选择这种语言的动机之一就是编写具有很高鲁棒性的软件,即完全定义的函数,数学上确定的函数,永不崩溃或产生错误。我并不是指由系统的谓词引起的失败("系统内存不足",#34;计算机着火"等等),这些都不是很有趣,可能会让整个系统崩溃处理。我也不是指由无效声明(pi = 4
)引起的错误行为。
相反,我指的是错误状态导致的错误,我希望通过严格的静态类型使这些状态不可表示和不可编译(在某些函数中)来删除。在我看来,我称这些功能为#34;纯粹的"并认为强类型系统可以让我实现这一目标。然而,Haskell没有定义" pure"以这种方式,允许程序在任何上下文中通过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水果,showFruit
会抛出错误。这是一个简单的解决方法,我们只需添加:
showFruit Peach = "A peach"
该程序现在编译没有警告,避免了危险!我们发射火箭但突然程序崩溃了:
Maybe.fromJust: Nothing
由于下面的错误线路导致火箭注定并击中海洋:
printFruit x = showFruit $ fromJust x
基本上fromJust
有一个分支,它会引发一个Error
所以我们甚至不想让程序编译,如果我们试图使用它,因为printFruit
绝对必须&#34;超&#34;纯。我们可以修复它,例如用以下代码替换:
printFruit x = maybe "Unknown fruit!" (\y -> showFruit y) x
我觉得奇怪的是,Haskell决定实施严格的打字和不完整的模式检测,这一切都在诺贝尔追求防止无效状态可以表现但都落在终点线之前,因为它没有给程序员一个检测分支的方法。 error
当他们不被允许时。从某种意义上说,这使得Haskell不如Java强大,这迫使你声明允许你的函数引发异常。
实现这一点的最简单的方法是以某种方式简单地定义error
,在函数本地使用,以及通过某种形式的相关声明在其等式中使用的任何函数。但是这似乎不可能。
The wiki page about errors vs exceptions提到了一个名为&#34; Extended Static Checking&#34;为此目的通过合同,但它只会导致链接断开。
它基本归结为:如何使上面的程序无法编译,因为它使用fromJust
?欢迎提出所有想法,建议和解决方案。
答案 0 :(得分:4)
Haskell允许任意的一般递归,因此如果只删除了各种形式的error
,那么所有Haskell程序都不一定是完全的。也就是说,您可以定义error a = error a
来处理您不想要处理的任何情况。运行时错误比无限循环更有帮助。
你不应该认为error
类似于花园种类的Java异常。 error
是表示编程错误的断言失败,可以调用fromJust
的函数error
是一个断言。您不应该尝试捕获error
生成的异常,除非在特殊情况下,即使请求的处理程序遇到编程错误,必须继续运行的服务器。
答案 1 :(得分:1)
您的主要投诉是关于fromJust
,但鉴于它基本上违背了您的目标,您为什么要使用它?
请记住,error
的最大原因是并非所有内容都可以通过类型系统可以保证的方式进行定义,因此有一个&#34;这不会发生,相信我&#34 ;说得通。 fromJust
来自于#34的思维模式;有时我比编译器更了解&#34;。如果你不同意这种心态,就不要使用它。或者,不要像这个例子那样滥用它。
编辑:
为了扩展我的观点,请考虑如何实现您的要求(使用部分功能的警告)。警告在以下代码中适用于哪里?
a = fromJust $ Just 1
b = Just 2
c = fromJust $ c
d = fromJust
e = d $ Just 3
f = d b
毕竟,所有这些都是静态的。 (对不起,如果下一个语法关闭,则为时已晚)
g x = fromJust x + 1
h x = g x + 1
i = h $ Just 3
j = h $ Nothing
k x y = if x > 0 then fromJust y else x
l = k 1 $ Just 2
m = k (-1) $ Just 3
n = k (-1) $ Nothing
i
这里再次安全,但j
不是。哪种方法应该返回警告?当我们的函数之外定义了部分或全部这些方法时,我们该怎么做。对k
有条件偏见的情况你做了什么?部分或全部这些功能是否会失败?
通过让编译器进行此调用,您会混淆许多复杂的问题。特别是当您考虑仔细选择库时,它会避免它。
在任何一种情况下,针对这些问题的IMO解决方案是在编译之后或期间使用工具来查找问题而不是修改编译器。这样的工具可以更容易地理解你的代码&#34; vs&#34;他们的代码&#34;并且更好地确定在不恰当使用时警告你的最佳位置。您也可以调整它,而无需在源文件中添加一堆注释以避免误报。我不会随便知道一个工具,或者我会建议一个。
答案 2 :(得分:1)
答案是,我想要的是Haskell不幸提供的总体检查水平(但是?)。
或者我想要依赖类型(例如Idris)或静态验证器(例如Liquid Haskell)或语法lint检测(例如hlint)。
我现在真的很喜欢伊德里斯,这似乎是一种令人惊叹的语言。这是我可以推荐观看的talk by the founder of Idris。
这些答案的归功于@duplode,@ chi,@ user3237465和@luqui。