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

问题描述:

我对Haskell相当陌生,并且一直在慢慢地认识到Monad的存在有问题。真实世界哈斯克尔warns against its use(“再一次,我们建议你几乎总是避免使用失败!”)。我今天才注意到Ross Paterson把它称为“一个疣,而不是一种设计模式”back in 2008(并且似乎在该主题中得到了相当多的一致意见)。我应该避免使用Monad失败吗?

在看RalfLämmel博士talk on the essence of functional programming的同时,我开始明白可能导致Monad失败的紧张局势。在讲座中,拉尔夫谈到了向基本monadic解析器(日志记录,状态等)添加各种monadic效果。许多效果需要对基本解析器进行更改,有时还需要使用所使用的数据类型。我认为,向所有单子添加“失败”可能是一种妥协,因为“失败”如此常见,并且您希望尽可能避免更改“基本”解析器(或其他)。当然,某种'失败'对于解析器来说是有意义的,但并不总是,例如,放置/获取状态或者询问读者本地。

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

我应该避免使用Monad失败吗? Monad失败的替代方法有哪些? 有没有其他monad库不包含这个“设计疣”? 我可以在哪里阅读关于这个设计决策历史的更多信息?

+4

[Real World Haskell says](http://book.realworldhaskell.org/read/monads.html#monads.monad.fail)“[in many monads]​​'fail' uses'error'。Calling'error'通常是非常不受欢迎的,因为它会抛出一个例外情况,呼叫者无法捕捉或不会期望。“ – Miikka

+3

简答:是的。 –

+2

我相信“失败”是需要处理模式匹配失败,当你像这样的东西:“只要x

一些单子具有明显的失败机制,例如,终端单子:

data Fail x = Fail 

一些单子没有合理的失效机理(undefined不是明智的),例如最初的单子:

data Return x = Return x 

在这个意义上,它显然要求所有的单子有fail方法的疣。如果你正在编写通过单子进行抽象的程序(Monad m) =>,那么使用这种通用的mfail方法并不是很健康。这会导致一个函数,你可以使用monadad实例化,其中fail应该不存在。

我在使用fail(尤其是间接地,通过匹配Pat <- computation)时发现,在明确指定了良好fail行为的特定monad中工作时,反对意见较少。这样的计划希望能够在回归旧纪元的过程中生存下来,其中非平凡的模式匹配创造了MonadZero而不仅仅是Monad的需求。

有人可能会争辩说,更好的纪律总是明确地对待失败案例。我反对这个观点有两点:(1)单点编程的要点是为了避免这样的混乱,以及(2)目前用于单子计算结果的案例分析符号太糟糕了。 SHE的下一个版本将支持符号(也可在其他变体中找到)

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

这可能会有所帮助。

但这整个情况是一团糟。通过它们支持的操作来表征monad通常是有帮助的。您可以看到failthrow等作为一些单子支持的操作,但不支持其他单元。 Haskell使得支持可用操作集中的小型本地化变更非常笨拙和昂贵,通过解释如何处理旧操作来引入新的操作。如果我们真的想在这里做一个更好的工作,我们需要重新思考如何工作,使其成为不同的本地错误处理机制之间的翻译。我经常想要在传递错误之前将一个计算失败(例如,通过模式匹配失败)来处理一个处理程序,该处理程序添加更多上下文信息。我不禁感到,有时候这样做比实际上要困难得多。

因此,这是一个可以做的更好的问题,但至少只能使用fail来提供明智实现的特定单子,并正确处理“例外”。

+0

不好意思,你提到的那个“SHE”是什么? –

+1

这是Strathclyde Haskell Enhancement,我的预处理器支持实验性功能(主要是假相关类型)。但是现在,通过将这些功能的大部分应用到GHC中,SHE已经变得多余。 – pigworker

在Haskell 1.4(1997)中没有fail。相反,有一个MonadZero类型的类包含zero方法。现在,do表示法使用zero来指示模式匹配失败;这给人们带来了惊喜:他们的功能是否需要MonadMonadZero取决于他们如何在其中使用do表示法。当Haskell 98被设计了一段时间后,他们做了一些改变,以使编程对新手更简单。例如,monad理解转化为列表解析。同样,要删除do类型的问题,类MonadZero已被删除;对于使用do,方法fail被添加到Monad;对于zero的其他用途,在MonadPlus中增加了mzero方法。

我认为这是一个很好的论据,fail不应该明确地用于任何事情;其唯一预期用途是翻译do表示法。不过,我自己也经常顽皮地使用fail

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

+0

“我自己经常调皮,明确使用'fail''。经常?说明。 –

+6

@丹伯顿:有什么可以解释的?如果我处于单一背景下,我倾向于使用“失败”来表示失败,除非我特别需要使用其他机制。 – ibid

+0

谢谢。很高兴听到有关历史。我不知道Haskell最初只有标记(没有列表解析)。我在邮件列表存档http://www.mail-archive.com/[email protected]/msg03002.html –

我尽量避免Monad失败,并且根据您的情况有多种捕获失败的方法。 Edward Yang在他的博客中撰写了一篇名为8 ways to report errors in Haskell revisited的文章。

综上所述,不同的方式来报告错误,他确定是:

  1. 使用错误
  2. 使用也许
  3. 请使用字符串一个
  4. 使用了Monad并不能一概而论1- 3
  5. 使用MonadError和自定义错误类型
  6. 使用抛出IO单元
  7. 使用ioError在和catch
  8. 想怎么样用单子变压器
  9. 经过异常
  10. 失败

这些,我会受到诱惑,如果我知道如何处理错误使用选项3,用Either e b ,但需要更多的上下文。

+1

或'也许'...使用任何一个都不错。尽管它使用一个使用Either的库和一个使用Maybe代码的库,但它很烦人。 Monad变形金刚不如... – alternative