Haskell接近错误处理 [英] Haskell approaches to error handling

查看:81
本文介绍了Haskell接近错误处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这里没有论据,Haskell中有各种各样的机制来处理错误并妥善处理它们。错误monad,或许,也许,异常等。



那么为什么它比其他语言写的异常容易的代码比Haskell更容易? p>

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




  • 验证文件名是否提供

  • 验证文件可用和可读

  • 验证文件有效标题

  • 创建输出文件夹并验证输出文件是否可写

  • 处理文件,

  • 输出文件,写入错误,磁盘已满等错误。



所以一个很直接的文件处理工具。



在Haskell中,我将使用Maybe和Either的一些组合来包装这些代码并根据需要翻译和传播错误。最后,它们都到达一个可以向用户输出状态的IO monad。



在另一种语言中,我只是抛出一个异常并捕获适当的地方。直截了当。我不会花很多时间在认知障碍试图解开我需要的机制的组合。



我只是接近这个错误,或者这里有一些实质的这种感觉?



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

解决方案


在Haskell中,我会包装这个使用Maybe和Either的一些组合的代码,并根据需要翻译和传播错误。最后,它们都到达一个可以向用户输出状态的IO monad。



在另一种语言中,我只是抛出一个异常并捕获适当的地方。直截了当。我不会花很多时间在认知障碍试图解开我需要的机制组合。


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



简单地抛出和捕获相当于对整个程序施加与Haskell的错误处理方法的组合完全相同的概念结构。确切的组合取决于您将其与之对比的语言的错误处理系统,这些系统与Haskell 似乎更为复杂:它可以让您根据需要混合和匹配错误处理结构而不是给你一个隐含的,一刀切的解决方案。



所以,如果你需要一个特定的错误处理方式,你可以使用它;并且您只将其用于需要的代码。不需要它的代码 - 由于既不生成也不处理相关类型的错误 - 被标记为这样,这意味着您可以使用该代码,而不必担心创建的那种错误。






关于语法笨拙的话题,这是一个尴尬的话题。理论上应该是无痛的,但是:




  • Haskell已经是一种研究驱动的语言了,而且在早期很多事情还在流行,有用的成语还没有被普及,所以旧的代码浮动可能是一个不好的榜样。

  • 有些图书馆没有那么灵活,如何处理错误,无论是由于上述旧代码的化石化,还是只是缺乏抛光

  • 我不知道有关如何最佳地构建新代码进行错误处理的指导,所以新手会留给自己的设备



我猜你有机会做错了避免大多数语法混乱,但是希望你(或任何一个平均的Haskell程序员)自己找到最好的方法可能是不合理的。



至于monad变压器堆栈,我认为标准的方法是 newtype 整个堆栈对于您的应用程序,派生或实现相关类型类的实例(例如, MonadError ),然后使用类型类的函数,通常不需要解除 ING。您应用的核心应用程序的Monadic功能都应该使用 newtype d堆栈,所以也不需要提升。关于唯一的低语义意义的事情,你不能避免的是 liftIO ,我想。



处理变压器大堆可以实际上是头痛,但只有当有很多不同变压器的嵌套层(堆叠交替层次为 StateT ErrorT 中间有一个 ContT ,然后只是告诉我你的代码实际上会做什么)。






编辑:作为次要的附录,我想提请注意在写几条评论时发生的更普遍的一点。



正如我所说和@sclv表现的很好,正确的错误处理真的是是,这很复杂。所有你可以做的是将这种复杂性混杂在一起,而不是消除它,因为无论你正在执行多个可以独立产生错误的操作,并且程序需要以某种方式处理所有可能的组合,即使这种处理只是简单地落下那么说,Haskell真的在某种程度上与大多数语言本质上是不同的:一般来说,错误处理都是 >显式第一类,这意味着所有内容都处于开放状态,可以自由操作。这样做的另一面是隐含的错误处理的丢失,这意味着即使你想要的是打印一条错误消息并且死亡,你必须明确地这样做。所以实际上,在Haskell中,错误处理更容易,因为它的一流抽象,但忽略错误更困难。然而,在任何形式的现实世界中,生产使用中,这种全手抛弃错误的错误处理方式几乎从不正确,这就是为什么看起来像尴尬的东西被拉到一边。 p>

所以,当你需要明确处理错误的时候,事情变得更加复杂,重要的是要记住,所有这些都是即可。一旦学习了如何使用正确的错误处理抽象,复杂性几乎达到了一个高原,并且程序扩展并不会变得更加困难;而且您使用的抽象越多,它们就越自然。


No argument here that there are a variety of mechanisms in place in Haskell to handle errors and properly handle them. Error monad, Either, Maybe, exceptions, etc.

So why is it that it feels much more straightforward writing exception-prone code in other languages than in Haskell?

Let's say I'd like to write a command line tool that processes files passed on the command line. I'd like to:

  • Verify filenames are provided
  • Verify files are available and readable
  • Verify files have valid headers
  • Create output folder and verify output files will be writable
  • Process files, erroring on parsing errors, invariant errors, etc.
  • Output files, erroring on write error, disk full, etc.

So a pretty straight file processing tool.

In Haskell, I'd be wrapping this code in some combination of monads, using Maybe's and Either's and translating and propagating errors as necessary. In the end, it all gets to an IO monad where I am able to output the status to the user.

In another language, I simply throw an exception and catch in the appropriate place. Straightforward. I don't spend much time in cognitive limbo trying to unravel what combination of mechanisms I need.

Am I simply approaching this wrong or is this there some substance to this feeling?

Edit: Okay, I'm getting feedback telling me that it just feels harder but actually isn't. So here is one pain point. In Haskell, I'm dealing with stacks of monads, and if I have to handle errors, I'm adding another layer to this monad stack. I don't know how many lift's and and other syntactic litter I've had to add just to make the code compile but adds zero semantic meaning. No one feels this adds to the complexity?

解决方案

In Haskell, I'd be wrapping this code in some combination of monads, using Maybe's and Either's and translating and propagating errors as necessary. In the end, it all gets to an IO monad where I am able to output the status to the user.

In another language, I simply throw an exception and catch in the appropriate place. Straightforward. I don't spend much time in cognitive limbo trying to unravel what combination of mechanisms I need.

I wouldn't say you're necessarily approaching it wrong. Rather, your mistake is in thinking that these two scenarios are different; they're not.

To "simply throw and catch" is equivalent to imposing upon your entire program the exact same conceptual structure as some combination of Haskell's error-handling methods. The exact combination depends on the error-handling systems of the language you're comparing it to, which points to why Haskell seems more complicated: It lets you mix and match error handling structures based on need, rather than giving you an implicit, one-size-fits-most solution.

So, if you need a particular style of error handling, you use it; and you use it for only the code that needs it. Code that doesn't need it--due to neither generating nor handling the relevant sorts of errors--is marked as such, meaning you can use that code without worrying about that sort of error being created.


On the subject of syntactic clumsiness, that's an awkward subject. In theory, it should be painless, but:

  • Haskell has been a research-driven language for a while, and in its early days many things were still in flux and useful idioms hadn't been popularized yet, so old code floating around is likely to be a poor role model
  • Some libraries are not as flexible as they could be in how errors are handled, either due to fossilization of old code as above, or just lack of polish
  • I'm not aware of any guides on how to best structure new code for error handling, so newcomers are left to their own devices

I'd guess that chances are you're "doing it wrong" somehow, and could avoid most of that syntactic clutter, but that it's probably not reasonable to expect you (or any average Haskell programmer) to find the best approaches on their own.

As far as monad transformer stacks go, I think the standard approach is to newtype the entire stack for your application, derive or implement instances for the relevant type classes (e.g., MonadError), then use the type class's functions which won't generally need lifting. Monadic functions you write for the core of your application should all use the newtyped stack, so won't need lifting, either. About the only low-semantic-meaning thing you can't avoid is liftIO, I think.

Dealing with large stacks of transformers can be an actual headache, but only when there's a lot of nested layers of different transformers (pile up alternating layers of StateT and ErrorT with a ContT tossed in the middle, then just try to tell me what your code will actually do). This is rarely what you actually want, though.


Edit: As a minor addendum, I want to bring attention to more general point that occurred to me while writing a couple comments.

As I remarked and @sclv demonstrated nicely, correct error-handling really is that complicated. All you can do is shuffle that complexity around, not eliminate it, because no matter what you're performing multiple operations that can produce errors independently and your program needs to handle every possible combination somehow, even if that "handling" is to simply fall over and die.

That said, Haskell really does differ intrinsically from most languages in one regard: Generally, error-handling is both explicit and first-class, meaning that everything is out in the open and can be manipulated freely. The flip side of this is a loss of implicit error-handling, meaning that even if all you want is to print an error message and die, you have to do so explicitly. So actually doing error-handling is easier in Haskell, because of first-class abstractions for it, but ignoring errors is harder. However, that sort of "all hands abandon ship" error non-handling is almost never correct in any sort of real-world, production use, which is why it seems like awkwardness gets brushed aside.

So, while it's true that things are more complicated at first when you need to deal with errors explicitly, the important thing is to remember that that's all there is to it. Once you learn how to use the proper error-handling abstractions, the complexity pretty much hits a plateau and doesn't really get significantly harder as a program expands; and the more you use those abstractions the more natural they become.

这篇关于Haskell接近错误处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆