Haskell库抛出异常背后的想法是什么

zor*_*119 11 haskell

为什么一个库(wreq404例如)抛出一个异常,而不是包装,结果弄成的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值,使可能出现的错误明确,只是这并不总是值得的.

  • FWIW,我的宠物理论是,在程序语言中发明/流行的例外原因是传统的程序语言使得表示,创造和消费和类型如此痛苦; 我经常希望C中的构造语法在语法上便宜,比如,例如 (2认同)
  • @Krom如果你忽略一个`Left`返回值,你就可以将一条(希望)描述性的错误信息变成一个难以理解的详尽模式异常.然后用_that_崩溃程序.如果忽略从操作本身抛出的异常,它将与(再次,_hopefully_)描述性异常消息一起崩溃. (2认同)
  • 在某些情况下(比如一个http包)我认为区分调用者几乎肯定想要在本地处理的条件(网络超时,页面未找到等)以及更有可能冒泡到的条件真的很有意义.更高级别的处理程序(由另一个线程抛出的异常,不存在的网络接口等). (2认同)