Exception Haskell错误处理方法

Exception Haskell错误处理方法,exception,haskell,Exception,Haskell,这里没有任何论据表明Haskell中有各种机制来处理错误并正确处理它们。错误monad,或者,可能,异常,等等 那么,为什么用其他语言编写易发生异常的代码比用Haskell编写代码感觉简单得多呢 假设我想编写一个命令行工具来处理在命令行上传递的文件。我想: 验证是否提供了文件名 验证文件是否可用且可读 验证文件是否具有有效的头 创建输出文件夹并验证输出文件是否可写 进程文件、解析错误错误、不变错误等 输出文件、写入错误、磁盘已满等 这是一个非常简单的文件处理工具 在Haskell中,我将把这

这里没有任何论据表明Haskell中有各种机制来处理错误并正确处理它们。错误monad,或者,可能,异常,等等

那么,为什么用其他语言编写易发生异常的代码比用Haskell编写代码感觉简单得多呢

假设我想编写一个命令行工具来处理在命令行上传递的文件。我想:

  • 验证是否提供了文件名
  • 验证文件是否可用且可读
  • 验证文件是否具有有效的头
  • 创建输出文件夹并验证输出文件是否可写
  • 进程文件、解析错误错误、不变错误等
  • 输出文件、写入错误、磁盘已满等
这是一个非常简单的文件处理工具

在Haskell中,我将把这段代码包装在一些单子的组合中,使用Maybe's和other's,并根据需要翻译和传播错误。最后,这一切都进入了一个IO monad,在那里我可以向用户输出状态

在另一种语言中,我只是在适当的位置抛出一个异常和catch。直截了当的我并没有花太多时间在认知的边缘试图解开我需要什么样的机制组合

我只是在错误地对待这一点,还是这种感觉有什么实质性的东西


编辑:好的,我得到的反馈告诉我,感觉很难,但实际上不是。这里有一个痛点。在Haskell中,我处理的是单子堆栈,如果我必须处理错误,我会在这个单子堆栈中添加另一层。我不知道为了使代码能够编译,我不得不添加了多少lift和其他语法垃圾,但添加了零语义。没有人觉得这增加了复杂性?

计算到
或是EA
和模式匹配,与
尝试
捕获
相比,除了它传播时有异常(如果使用任一单子,您可以模拟这一点)之外,还有什么区别

请记住,在我看来,某些东西的一元用法在大多数情况下都是丑陋的,除非你大量使用可能会失败的函数

如果你只有一次可能的失败,那么你的工作就没有问题

func x = case tryEval x of
             Left e -> Left e
             Right val -> Right $ val + 1

func x = (+1) <$> trvEval x
func x=案例tryEval x
左e->左e
右val->右$val+1
func x=(+1)trvEval x
这只是表示同一事物的一种功能性方式

在Haskell中,我将把这段代码包装在一些单子的组合中,使用Maybe's和other's,并根据需要翻译和传播错误。最后,这一切都进入了一个IO monad,在那里我可以向用户输出状态

在另一种语言中,我只是在适当的位置抛出一个异常和catch。直截了当的我并没有花太多时间在认知的边缘试图解开我需要什么样的机制组合

我不会说你一定是走错路了。相反,你的错误在于认为这两种情况是不同的;他们不是

“简单地抛出并捕获”相当于将与Haskell的错误处理方法的某些组合完全相同的概念结构强加给整个程序。确切的组合取决于您将其与之进行比较的语言的错误处理系统,这说明了Haskell看起来更复杂的原因:它允许您根据需要混合和匹配错误处理结构,而不是提供一个隐含的、一刀切的解决方案

因此,如果您需要一种特殊的错误处理方式,您可以使用它;而你只在需要它的代码中使用它。由于既不生成也不处理相关类型的错误而不需要它的代码被标记为这样,这意味着您可以使用该代码,而不必担心会创建此类错误


在句法笨拙的问题上,这是一个尴尬的话题。理论上,它应该是无痛的,但是:

  • Haskell作为一种研究驱动的语言已经有一段时间了,在它的早期,许多东西仍在不断变化,有用的习惯用法还没有普及,所以到处流传的旧代码可能是一个糟糕的榜样
  • 有些库在处理错误的方式上不够灵活,这可能是由于上面提到的旧代码僵化,也可能是因为缺少润色
  • 我不知道有任何关于如何为错误处理最佳地构造新代码的指南,所以新手只能靠自己的设备
我猜你可能在某种程度上“做错了”,并且可以避免大部分语法混乱,但是期望你(或任何普通Haskell程序员)自己找到最好的方法可能是不合理的

就monad transformer堆栈而言,我认为标准方法是
newtype
应用程序的整个堆栈,派生或实现相关类型类的实例(例如,
MonadError
),然后使用类型类的函数,这些函数通常不需要
lift
ing。为应用程序核心编写的一元函数都应该使用
newtype
d堆栈,因此也不需要提升。我认为,唯一一件你无法避免的低语义的事情是
liftIO

处理大型的变压器堆栈可能是一个实际的问题,但是只有当有很多不同的变压器的嵌套层(堆叠层的代码< > STATET < /代码>和<代码> ErrorT < /代码>一个<代码> ContT <代码>在中间抛出,然后试着告诉我你的代码将实际做什么)。不过,这很少是你真正想要的


编辑:作为一个小附录,我想提请大家注意我在写几条评论时遇到的更一般的问题

正如我所说,@sclv很好地演示的,正确的错误处理确实是那么复杂。艾尔