为什么一个库(wreq在404例如)抛出一个异常,而不是包装,结果弄成的Maybe?
天真地,我认为Maybe会更好(编译器警告我,如果我没有处理所有的情况例如).我为什么在这里错了?
lef*_*out 15
Haskellers确实努力避免从函数中抛出异常- 函数应该只在异常情况下抛出异常,即在"这应该永远不会发生"的情况下,例如用户传递文档明确禁止的一些输入.如果纯函数经常抛出异常,这将是一个大问题,不仅因为类型没有说明应该准备捕获什么,也不能在纯函数中捕获异常,而只能在IO调用函数的代码中捕获异常.即使你原则上可以捕获异常,也可能很难预测它需要在哪里完成,因为懒惰的评估可能会延迟实际发生的点.
事实上,即使在例如情况下Wreq.get,也不会从函数中抛出异常.
Prelude Network.Wreq> get "htt:/p/this-isn't even valid URL syntax" `seq` "Ok"
"Ok"
Run Code Online (Sandbox Code Playgroud)
执行它时,它是抛出的IO动作:
Prelude Network.Wreq> get "htt:/p/this-isn't even valid URL syntax" >> pure ()
*** Exception: InvalidUrlException "htt:/p/this-isn't%20even%20valid%20URL%20syntax" "Invalid scheme"
Run Code Online (Sandbox Code Playgroud)
现在有一个IO动作,情况有点不同.大量的IO动作可以有可能是难以或不可能预料,就像一个硬盘驱动器崩溃不同的情况可能非常不同的错误.为每个动作编制适当数据类型中的所有可能错误的编目将是一项重大任务,处理每个可能的情况或确定哪些部分只是传递将是非常麻烦的.简单地将每个IO动作的结果包装起来 Maybe只会导致类似Java的情况,其中每个引用都可能为空.这并没有告诉你任何事情,人们通常也不会提出合理的方法来处理这个问题.
这就是为什么首先发明异常的问题,并且它对于过程语言也适用于Haskell(或者更确切地说,它是程序性的eDSL IO).并且因为与纯函数不同IO,它的控制流中确实有一个明确定义的时序,如果你需要捕获一些特殊的异常,你也必须清楚地去做.
这并不是说是没有意义一个IO动作返回Maybe/ Either值,使可能出现的错误明确,只是这并不总是值得的.