我应该避免使用Monad失败吗?
我对Haskell相当陌生,并且一直在慢慢认识到Monad的存在出现问题。 真实世界Haskell警告不要使用它(“再一次,我们建议你几乎总是避免使用失败!”)。 我今天才注意到,罗斯帕特森在2008年把它称为“一种疣,而不是一种设计模式”(并且似乎在该主题中达成了一些共识)。
在观看RalfLämmel博士谈论函数式编程的本质时,我开始理解可能导致Monad失败的可能的紧张。 在讲座中,拉尔夫谈到了向基本monadic解析器(日志记录,状态等)添加各种monadic效果。 许多效果需要对基本解析器进行更改,有时还需要使用所使用的数据类型。 我认为,向所有单子添加“失败”可能是一种妥协,因为“失败”如此常见,并且您希望尽可能避免更改“基本”解析器(或其他)。 当然,某种“失败”对于解析器来说是有意义的,但并不总是,比如说,放置/获取状态,或者询问读者本地。
让我知道我是否可能走错了路。
我应该避免使用Monad失败吗? Monad有哪些替代方法会失败? 有没有其他monad库不包含这个“设计疣”? 我可以在哪里阅读关于这个设计决策历史的更多信息?
一些monad有一个明智的失败机制,例如终端monad:
data Fail x = Fail
一些monad没有明智的失败机制( undefined
不明智),例如最初的monad:
data Return x = Return x
从这个意义上说,显然要求所有单子都有fail
方法。 如果你正在编写通过monad (Monad m) =>
抽象的程序,那么利用这个泛型m
的fail
方法并不是很健康。 这将导致一个函数,你可以用monad实例化fail
应该存在的地方。
在特定monad中工作时,我发现对使用fail
的反对意见更少(特别是间接地,通过匹配Pat <- computation
),并且明确指定了良好的fail
行为。 这样的程序希望能够在恢复到原来的纪律之后继续存在,在这种纪律下,非平凡的模式匹配创造了MonadZero
而不是Monad
的需求。
有人可能会争辩说,更好的纪律总是明确地对待失败案例。 我反对这个观点有两点:(1)单点编程的要点是为了避免这样的混乱,以及(2)目前用于单子计算结果的案例分析符号太糟糕了。 SHE的下一个版本将支持符号(也可以在其他变体中找到)
case <- computation of
Pat_1 -> computation_1
...
Pat_n -> computation_n
这可能会有所帮助。
但是这整个情况是一团糟。 通过它们支持的操作来表征monad通常是有帮助的。 你可以看到一些单子支持的操作fail
, throw
等,但不是其他单元。 Haskell使得支持可用操作集中的小型本地化变更非常笨拙和昂贵,通过解释如何处理旧操作来引入新的操作。 如果我们真的想在这里做一个更好的工作,我们需要重新思考catch
如何工作,使它成为不同本地错误处理机制之间的翻译者。 我经常想要将一个计算失败(例如,通过模式匹配失败)来处理可能失败的计算,并在传递错误之前添加更多上下文信息。 我不禁感到,有时候这样做比实际上要困难得多。
所以,这是一个可能-做更好的问题,但最起码,使用fail
只为它提供一个合理的实现特定的单子,并办理“例外”正确。
在Haskell 1.4(1997)中,并没有fail
。 相反,有一个包含zero
方法的MonadZero
类型类。 现在, do
记号用zero
来表示模式匹配失败; 这引起了惊喜的人:他们的功能是否需要Monad
或MonadZero
取决于他们如何使用do
记号在里面。
当Haskell 98稍后设计时,他们做了一些改变,以使编程对新手更简单。 例如,monad理解转化为列表解析。 同样,为了消除do
类的问题, MonadZero
类被删除; 为了使用do
,该方法fail
被添加到Monad
; 和用于其它用途zero
,一mzero
方法加入到MonadPlus
。
我认为,应该提出一个很好的论点,即fail
应该明确地使用任何fail
; 其唯一的用途是在翻译do
记号。 尽管如此,我自己也经常顽皮而且使用fail
。
您可以在这里访问原始的1.4和98报告。 我确信导致改变的讨论可以在一些电子邮件清单档案中找到,但我没有方便的链接。
我尽量避免Monad失败,并根据您的情况采取各种方式来捕获失败。 Edward Yang在他的博客中撰写了一篇很好的综述,文章名为“重新编写Haskell中的错误报告的8种方法”。
总之,报告错误的方法有以下几种:
其中,我会尝试使用选项3,用Either eb
如果我知道如何处理错误,但需要多一点的环境。
上一篇: Should I avoid using Monad fail?
下一篇: Comparison of Priority Queue implementations in Haskell