如果我理解正确,Haskell中的异常基本上是为了处理IO monad.至少可以在IO monad中捕获异常.
但有时即使是纯函数也可能抛出异常,例如read "..." :: Int(当读取字符串不表示整数时),运算符(!!)(当我们试图使项目超出列表范围时),等等.这是真实的行为,我不否认.但是,我不想更改函数的签名只是为了抓住可能的异常,因为在这种情况下我必须通过调用堆栈更改签名所有函数.
在IO monad中,是否有一些模式可以处理Haskell中更为舒适的异常?可能我应该unsafePerformIO在这种情况下使用?如何"安全" unsafePerformIO仅用于捕获纯函数中的异常?
ham*_*mar 10
在纯代码中,通常最好避免异常发生在一开始.也就是说,head除非你绝对肯定列表不为空,并且使用reads和模式匹配而不是read检查解析错误,否则不要使用.
我认为一个好的经验法则是纯代码中的异常应该只来自编程错误,即调用error,这些应该被视为错误,而不是异常处理程序可以处理的错误.
请注意,我在这里只讨论纯代码,在IO处理与"真实世界"交互时有时会发生的异常情况时,异常会有用.然而,纯机制喜欢Maybe和ErrorT比较容易的工作,因此他们常首选.
这就是monad的用途!(好吧,不仅如此,异常处理是monadic习语的一种用法)
您确实更改了可能失败的函数的签名(因为它们会更改语义,并且您希望在类型中尽可能多地反映语义,作为经验法则).但是使用这些函数的代码不必在每个可用函数上进行模式匹配; 如果他们不在乎,他们可以绑定:
head :: [a] -> Maybe a
eqHead :: (Eq a) => [a] -> Maybe [a]
eqHead xs = do
h <- head xs
return $ filter (== h) xs
Run Code Online (Sandbox Code Playgroud)
所以eqHead不能写成"纯粹的"(一种语法选择,我希望看到它的替代方案),但它也不必真正了解它的Maybe含义head,只需要知道它head可能会以某种方式失败.
它并不完美,但我们的想法是Haskell中的函数与Java没有相同的风格.在典型的Haskell设计中,异常通常不会发生在调用链的深处.相反,当我们知道所有参数都是完全定义且表现良好的时候,深度调用链都是纯粹的,并且验证发生在最外层.因此,从深层冒出来的异常并不是真正需要在实践中得到支持的东西.
这样做的难度是否会导致设计模式,或者设计模式是否导致缺乏支持这一功能的特征是一个有争议的问题.