如果我理解正确,Haskell中的异常基本上是为了处理IO monad。至少可以在IO monad中捕获异常。
但有时即使是纯函数也可能抛出异常,例如read "..." :: Int
(当读取字符串不表示整数时),运算符(!!)
(当我们尝试使项目超出列表范围时),等等。这是真实的行为,我不否认。但是,我不想更改函数的签名只是为了抓住可能的异常,因为在这种情况下我必须通过调用堆栈更改签名所有函数。
在IO monad中,是否有一些模式可以处理Haskell中更为舒适的异常?在这种情况下,我应该使用unsafePerformIO
吗?使用unsafePerformIO
仅仅用于捕获纯函数中的异常是多么“安全”?
答案 0 :(得分:10)
在纯代码中,通常最好避免首先发生异常。也就是说,不要使用head
,除非你绝对肯定列表不为空,并使用reads
和模式匹配而不是read
来检查解析错误。< / p>
我认为一个好的经验法则是纯代码中的异常应该只来自编程错误,即调用error
,这些应该被视为错误,而不是异常处理程序可以处理的内容。 / p>
请注意,我在这里只讨论纯代码,IO
中的异常在处理与“真实世界”交互时有时会发生的异常情况时有其用途。但是,Maybe
和ErrorT
这样的纯机制更容易使用,因此通常更受欢迎。
答案 1 :(得分:5)
那是monad的用途! (好吧,不仅仅是这样,但异常处理是monadic习语的一种用法)
你做更改可能失败的函数的签名(因为它们会改变语义,并且您希望尽可能多地反映类型中的语义,作为经验法则)。但是使用这些函数的代码不必在每个可用函数上进行模式匹配;如果他们不在乎,他们可以绑定:
head :: [a] -> Maybe a
eqHead :: (Eq a) => [a] -> Maybe [a]
eqHead xs = do
h <- head xs
return $ filter (== h) xs
所以eqHead
无法写成&#34;纯粹&#34; (一种语法选择,我希望看到它的替代方案),但它也不必真正了解Maybe
的{{1}} - ness,它只需要知道head
1}}可能会以某种方式失败。
它并不完美,但我们的想法是Haskell中的函数与Java没有相同的风格。在典型的Haskell设计中,异常通常不会发生在调用链的深处。相反,当我们知道所有参数都是完全定义且表现良好的时候,深度调用链都是纯粹的,并且验证发生在最外层。因此,从深层冒出的异常并不是真正需要在实践中支持的东西。
这样做的难度是否会导致设计模式,或设计模式是否导致缺乏支持这一功能的功能是一个有争议的问题。
答案 2 :(得分:3)
如果您预计read
之类的函数可能会导致异常,那么为什么不简单地重构您的代码以避免发生异常的可能性?
作为对您问题的更直接的回答,有spoon。
答案 3 :(得分:2)
我将走出困境,强烈反对那些说解决方案是“首先避免出现错误”的人的意见。我会构建你的代码来处理这些monad中的错误。
纯函数(和FP)的主要优势之一是能够推理代码,“如果函数具有类型[a] -> a
,那么所有类型a
的列表,我将返回类型为a
的值。“像这样的例外情况会从那个方面削减出来。
head
成为现实的一个很好的理由是,初学者在Maybe
和朋友之前学习列表操作要简单得多。但如果你能理解一种更好的方法,我就会避免这些风险。