我应该避免使用 Monad 失败吗?

发布于 2024-12-16 14:28:59 字数 939 浏览 2 评论 0原文

我对 Haskell 相当陌生,并且慢慢地意识到 Monad failure 的存在有问题。 Real World Haskell 警告不要使用它(“我们再次建议你几乎总是避免使用失败!”)。我今天才注意到罗斯帕特森称其为“一个疣,而不是一个设计模式” 早在 2008 年(似乎在这一点上达成了相当多的一致) 线)。

在观看 Ralf Lämmel 博士谈论函数式编程的本质,我开始理解可能导致 Monad 失败的一种紧张关系。在讲座中,Ralf 谈到了向基础单子解析器添加各种单子效果(日志记录、状态等)。许多效果需要更改基本解析器,有时还需要更改所使用的数据类型。我认为向所有单子添加“失败”可能是一种妥协,因为“失败”非常常见,并且您希望尽可能避免对“基本”解析器(或其他)进行更改。当然,某种“失败”对于解析器来说是有意义的,但并不总是如此,例如 State 的 put/get 或 Reader 的 Ask/local。

让我知道我是否走错了路。

我应该避免使用 Monad 失败吗? Monad 失败的替代方案是什么? 是否有任何不包含此“设计疣”的替代 monad 库? 我在哪里可以了解有关此设计决策的历史的更多信息?

I'm fairly new to Haskell and have been slowly getting the idea that there's something wrong with the existence of Monad fail. Real World Haskell warns against its use ("Once again, we recommend that you almost always avoid using fail!"). I just noticed today that Ross Paterson called it "a wart, not a design pattern" back in 2008 (and seemed to get quite some agreement in that thread).

While watching Dr Ralf Lämmel talk on the essence of functional programming, I started to understand a possible tension which may have led to Monad fail. In the lecture, Ralf talks about adding various monadic effects to a base monadic parser (logging, state etc). Many of the effects required changes to the base parser and sometimes the data types used. I figured that the addition of 'fail' to all monads might have been a compromise because 'fail' is so common and you want to avoid changes to the 'base' parser (or whatever) as much as possible. Of course, some kind of 'fail' makes sense for parsers but not always, say, put/get of State or ask/local of Reader.

Let me know if I could be off onto the wrong track.

Should I avoid using Monad fail?
What are the alternatives to Monad fail?
Are there any alternative monad libraries that do not include this "design wart"?
Where can I read more about the history around this design decision?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

不疑不惑不回忆 2024-12-23 14:28:59

一些 monad 具有合理的故障机制,例如终端 monad:

data Fail x = Fail

一些 monad 没有合理的故障机制(未定义不合理),例如初始 monad:

data Return x = Return x

从这个意义上说,它显然是一个疣要求所有 monad 都有一个 fail 方法。如果您正在编写对 monad 进行抽象的程序 (Monad m) =>,那么使用通用 mfail< 并不是很健康。 /代码> 方法。这将导致您可以使用 monad 实例化一个函数,其中 fail 不应该真正存在。

当在特定的 monad 中工作时,我发现很少有人反对使用 fail(特别是间接地,通过匹配 Pat <- 计算),而该 monad 的良好 fail > 行为已被明确规定。这样的程序有望在回到旧规则的情况下继续存在,在旧规则中,重要的模式匹配创建了对 MonadZero 而不仅仅是 Monad 的需求。

有人可能会说,更好的纪律总是明确地处理失败案例。我反对这个立场有两个原因:(1)一元编程的目的是避免这种混乱,(2)当前对一元计算结果进行案例分析的表示法非常糟糕。 SHE 的下一个版本将支持该符号(也在其他变体中找到),

case <- computation of
  Pat_1 -> computation_1
  ...
  Pat_n -> computation_n

这可能会有所帮助。

但这整个情况真是一团糟。通过单子支持的操作来表征单子通常很有帮助。您可以将 failthrow 等视为某些 monad 支持的操作,但其他 monad 不支持。 Haskell 使得支持可用操作集中的小型局部更改变得非常笨拙和昂贵,通过解释如何根据旧操作来处理新操作来引入新操作。如果我们真的想在这里做得更整洁,我们需要重新考虑catch的工作原理,使其成为不同本地错误处理机制之间的转换器。我经常想将可能在无信息的情况下失败(例如,由于模式匹配失败)的计算与一个处理程序括起来,该处理程序在传递错误之前添加更多上下文信息。我不禁感到有时这样做比应有的更困难。

因此,这是一个可以做得更好的问题,但至少,仅对提供合理实现的特定 monad 使用 fail ,并正确处理“异常”。

Some monads have a sensible failure mechanism, e.g. the terminal monad:

data Fail x = Fail

Some monads don't have a sensible failure mechanism (undefined is not sensible), e.g. the initial monad:

data Return x = Return x

In that sense, it's clearly a wart to require all monads to have a fail method. If you're writing programs that abstract over monads (Monad m) =>, it's not very healthy to make use of that generic m's fail method. That would result in a function you can instantiate with a monad where fail shouldn't really exist.

I see fewer objections to using fail (especially indirectly, by matching Pat <- computation) when working in a specific monad for which a good fail behaviour has been clearly specified. Such programs would hopefully survive a return to the old discipline where nontrivial pattern matching created a demand for MonadZero instead of just Monad.

One might argue that the better discipline is always to treat failure-cases explicitly. I object to this position on two counts: (1) that the point of monadic programming is to avoid such clutter, and (2) that the current notation for case analysis on the result of a monadic computation is so awful. The next release of SHE will support the notation (also found in other variants)

case <- computation of
  Pat_1 -> computation_1
  ...
  Pat_n -> computation_n

which might help a little.

But this whole situation is a sorry mess. It's often helpful to characterize monads by the operations which they support. You can see fail, throw, etc as operations supported by some monads but not others. Haskell makes it quite clumsy and expensive to support small localized changes in the set of operations available, introducing new operations by explaining how to handle them in terms of the old ones. If we seriously want to do a neater job here, we need to rethink how catch works, to make it a translator between different local error-handling mechanisms. I often want to bracket a computation which can fail uninformatively (e.g. by pattern match failure) with a handler that adds more contextual information before passing on the error. I can't help feeling that it's sometimes more difficult to do that than it should be.

So, this is a could-do-better issue, but at the very least, use fail only for specific monads which offer a sensible implementation, and handle the 'exceptions' properly.

椒妓 2024-12-23 14:28:59

在 Haskell 1.4 (1997) 中没有失败。相反,有一个包含 zero 方法的 MonadZero 类型类。现在,do 表示法使用零来指示模式匹配失败;这引起了人们的惊讶:他们的函数是否需要 Monad 还是 MonadZero 取决于他们如何在其中使用 do 表示法。

稍后设计 Haskell 98 时,他们做了一些更改,以使编程对新手来说更简单。例如,单子推导式被转变为列表推导式。同样,为了消除 do 类型类问题,删除了 MonadZero 类;对于do的使用,在Monad中添加了fail方法;对于 zero 的其他用途,MonadPlus 中添加了 mzero 方法。

我认为,有一个很好的论点,即 fail 不应该明确用于任何事情;它唯一的预期用途是翻译 do 表示法。尽管如此,我自己也经常顽皮地使用 fail

您可以在此处访问原始 1.4 和 98 报告。我确信导致更改的讨论可以在一些电子邮件列表档案中找到,但我没有方便的链接。

In Haskell 1.4 (1997) there was no fail. Instead, there was a MonadZero type class which contained a zero method. Now, the do notation used zero to indicate pattern match failure; this caused surprises to people: whether their function needed Monad or MonadZero depended on how they used the do notation in it.

When Haskell 98 was designed a bit later, they did several changes to make programming simpler to the novice. For example, monad comprehensions were turned into list comprehensions. Similarly, to remove the do type class issue, the MonadZero class was removed; for the use of do, the method fail was added to Monad; and for other uses of zero, a mzero method was added to MonadPlus.

There is, I think, a good argument to be made that fail should not be used for anything explicitly; its only intended use is in the translation of the do notation. Nevertheless, I myself am often naughty and use fail explicitly, too.

You can access the original 1.4 and 98 reports here. I'm sure the discussion leading to the change can be found in some email list archives, but I don't have links handy.

银河中√捞星星 2024-12-23 14:28:59

我会尽可能避免 Monad 失败,并且根据您的具体情况,有多种方法可以捕获失败。 Edward Yang 在他的博客上发表了一篇题为 重新审视 Haskell 中报告错误的 8 种方法

总之,他确定的报告错误的不同方法是:

  1. 使用错误
  2. 使用 Maybe a
  3. 使用 Either String a
  4. 使用 Monad 并无法泛化 1-3
  5. 使用 MonadError 和自定义错误类型
  6. 在 IO monad 中使用 throw
  7. 使用 ioError 并捕获
  8. Go带有 monad 转换器的坚果
  9. 检查异常
  10. 失败

其中,如果我知道如何处理错误,我会尝试使用选项 3,即 Either e b,但需要更多上下文。

I try to avoid Monad fail wherever possible, and there are a whole variety of ways to capture fail depending on your circumstance. Edward Yang has written a good overview on his blog in the article titled 8 ways to report errors in Haskell revisited.

In summary, the different ways to report errors that he identifies are:

  1. Use error
  2. Use Maybe a
  3. Use Either String a
  4. Use Monad and fail to generalize 1-3
  5. Use MonadError and a custom error type
  6. Use throw in the IO monad
  7. Use ioError and catch
  8. Go nuts with monad transformers
  9. Checked exceptions
  10. Failure

Of these, I would be tempted to use option 3, with Either e b if I know how to handle the error, but need a little more context.

樱娆 2024-12-23 14:28:59

如果您使用 GHC 8.6.1 或更高版本,则会有一个名为 MonadFail 的新类和一个名为 MonadFailDesugaring 的语言扩展。如果您有权访问这些内容,请使用它们。然后,您可以使用 fail ,而不会出现某些 monad 实际上没有失败实现的问题。

此扩展使得 fail 脱糖需要更严格的 MonadFail 而不是任何 monad。

If you use GHC 8.6.1 or above, there is a new class called MonadFail and a language extension called MonadFailDesugaring. If you have access to these, use them. You can then use fail without a problem that some monad actually has no fail implementation.

This extension makes the fail desugaring requires a more restrictive MonadFail instead of any monad.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文