我对Haskell还是相当陌生,并且一直慢慢地想到Monad的存在是有问题的。现实世界中的Haskell warns against its use(“再次,我们建议您几乎总是避免使用fail!”)。我今天刚刚注意到,罗斯·帕特森(Ross Paterson)称其为“疣,而不是设计模式” back in 2008(并且似乎在该线程中获得了一些共识)。
在观看RalfLämmeltalk on the essence of functional programming博士时,我开始理解可能导致Monad失败的紧张局势。在演讲中,拉尔夫谈到了将各种单调效果添加到基本单调解析器(记录,状态等)。许多影响需要更改基本解析器,有时还需要更改所使用的数据类型。我认为向所有单子(monad)添加“fail”可能是一个折衷方案,因为“fail”非常常见,并且您希望尽可能避免对“基本”解析器(或其他任何东西)进行更改。当然,某种“失败”对于解析器来说是有意义的,但并非总是如此,例如,放置/获取状态或询问/询问读者。
让我知道我是否走错了路。
我应该避免使用Monad失败吗?
Monad失败的替代方法有哪些?
是否有不包含此“设计疣”的替代monad库?
在哪里可以阅读有关此设计决策历史的更多信息?
最佳答案
有些单子(monad)有一个明智的失败机制,例如终端monad:
data Fail x = Fail
有些monad没有明智的失败机制(
undefined
不明智),例如初始monad:data Return x = Return x
从这个意义上讲,要求所有monad具有
fail
方法显然是一种疣。如果您正在编写对monads (Monad m) =>
进行抽象的程序,那么使用该通用m
的fail
方法不是很健康。这将导致您可以使用一个单子(monad)实例化一个函数,而fail
不应真正存在。当在明确指定了良好
fail
行为的特定monad中工作时,我看到较少反对使用Pat <- computation
(尤其是通过匹配fail
间接使用)。这样的程序有望在回到旧的学科中幸存下来,在旧学科中,非平凡的模式匹配导致了对MonadZero
的需求,而不仅仅是Monad
的需求。有人可能会说,更好的纪律总是要明确对待失败案例。我在两个方面反对这个立场:(1)单子(monad)程序设计的目的是避免这种困惑,(2)当前关于单子(monad)计算结果的案例分析的注释是如此糟糕。 SHE的下一发行版将支持该符号(也在其他变体中找到)
case <- computation of
Pat_1 -> computation_1
...
Pat_n -> computation_n
这可能会有所帮助。
但是,这整个情况令人遗憾。通过monad支持的操作来表征monad通常很有帮助。您可以将
fail
,throw
等视为某些单子(monad)命令支持的操作,而其他命令则不支持。 Haskell使得在可用的操作集中支持小的局部更改变得非常笨拙和昂贵,通过解释如何按照旧操作来处理新操作来引入新操作。如果我们真的想在这里做得更整洁,则需要重新考虑catch
的工作方式,以使其成为不同本地错误处理机制之间的转换程序。我经常想用运算符将可能导致信息失败(例如由于模式匹配失败)的计算括起来,该处理程序在传递错误之前添加更多上下文信息。我忍不住觉得有时候做起来比应该做的难。因此,这是一个可以做的更好的问题,但是至少,仅对提供合理实现并适当处理“异常”的特定monad使用
fail
。