Answers:
使用Maybe
(或其Either
工作原理基本相同,但可以让您返回任意值代替的表亲Nothing
)的用途与异常的用途略有不同。用Java术语来说,就像有一个检查异常而不是运行时异常。它代表您必须处理的某些期望,而不是您没有预期的错误。
这样的函数indexOf
会返回一个Maybe
值,因为您希望该项目不在列表中。这类似于null
从函数返回,只是以类型安全的方式强制您处理null
情况。Either
除了您可以返回与错误情况相关的信息外,其他方法的工作方式相同,因此实际上与异常更类似Maybe
。
那么Maybe
/ Either
方法的优点是什么?首先,它是该语言的一等公民。让我们将一个函数Either
与一个引发异常的函数进行比较。对于例外情况,您唯一的真实诉求是try...catch
声明。对于此Either
功能,您可以使用现有的组合器使流量控制更加清晰。以下是几个示例:
首先,假设您要尝试几种可能连续出错的功能,直到获得没有出错的功能。如果您没有得到任何正确的消息,则想返回一条特殊的错误消息。这实际上是一个非常有用的模式,但使用会造成极大的痛苦try...catch
。令人高兴的是,由于Either
这只是一个正常值,因此您可以使用现有函数使代码更清晰:
firstThing <|> secondThing <|> throwError (SomeError "error message")
另一个示例是具有可选功能。假设您有多个要运行的功能,其中包括一个试图优化查询的功能。如果失败,则您希望其他所有内容继续运行。您可以编写如下代码:
do a <- getA
b <- getB
optional (optimize query)
execute query a b
与使用相比try..catch
,这两种情况都更清楚,更短,更重要的是,还具有更多的语义。与始终处理异常相比,使用类似<|>
或之类的功能optional
可以使您的意图更加清晰try...catch
。
另请注意,您不必在代码中乱码if a == Nothing then Nothing else ...
!治疗的整点Maybe
和Either
一个单子为的是避免这种情况。您可以将传播语义编码到bind函数中,以便免费获得null /错误检查。唯一需要显式检查的是,是否要返回Nothing
给定以外的内容Nothing
,即使这样也很容易:有很多标准库函数可以使代码更好。
最后,另一个优点是Maybe
/ Either
类型更简单。无需使用其他关键字或控件结构来扩展语言-一切都只是一个库。由于它们只是普通值,因此使类型系统更简单-在Java中,您必须区分throws
不使用的类型(例如,返回类型)和效果(例如,语句)Maybe
。它们的行为也与任何其他用户定义的类型一样—不需要在语言中嵌入特殊的错误处理代码。
另一个胜利是Maybe
/ Either
是函子和monad,这意味着它们可以利用现有的monad控制流功能(其中有一个相当大的数目),并且通常可以与其他monad一起很好地玩耍。
也就是说,有一些警告。其一,既不Maybe
也不Either
更换未经检查的异常。您将需要其他方法来处理诸如除以0之类的事情,这仅仅是因为让每个除法都返回一个Maybe
值会很痛苦。
另一个问题是有多种类型的错误返回(仅适用于Either
)。使用异常,您可以在同一函数中引发任何不同类型的异常。使用Either
,您只会得到一种类型。这可以通过子类型化或包含所有不同类型错误的ADT作为构造函数来克服(第二种方法是Haskell中通常使用的方法)。
总体而言,我还是更喜欢Maybe
/ Either
方法,因为我发现它更简单,更灵活。
OpenFile()
可以抛出FileNotFound
或NoPermission
或TooManyDescriptors
等。没有不携带这种信息。if None return None
-style语句。最重要的是,异常和Maybe monad具有不同的用途-异常用于表示问题,而Maybe不是。
“护士,如果 5室有病人,您能请他等吗?”
(请注意“如果”-这表示医生正在期待 Maybe monad)
None
值可以被传播)。您的观点5只是一种权利……问题是:哪些情况无疑是例外?事实证明…… 不多。
bind
:测试None
不会产生语法开销。一个非常简单的示例,C#只是Nullable
适当地重载了运算符。None
即使使用类型,也无需检查。当然,检查仍然可以完成(它是安全的类型),但是在幕后并不会使您的代码混乱。从某种意义上说,您对我对(5)的反对也同样适用,但我同意这可能并不总是适用。
Maybe
视为单子的全部要点是使传播None
隐式。这意味着,如果您要返回None
给定None
,则根本不必编写任何特殊代码。您唯一需要匹配的时间是如果您想对进行特殊操作None
。您永远不需要if None then None
某种陈述。
null
完全一样的检查(例如if Nothing then Nothing
),因为它是monad。它在的bind()定义中编码。Maybe
>>=
Maybe
Either
行为类似的错误信息(例如)Maybe
。实际上,两者之间的切换非常简单,因为Maybe
实际上只是的一种特殊情况Either
。(在Haskell,你能想到的Maybe
作为Either ()
。)
“也许”不能替代例外。异常应在特殊情况下使用(例如:打开数据库连接,但应该有数据库服务器不存在)。“也许”用于为您可能具有或没有有效值的情况建模;说您正在从字典中获得某个键的值:它可能存在或可能不存在-这些结果中的任何一个都没有“例外”。
我是蒂洪的第二个回答,但我认为每个人都缺少一个非常重要的实际要点:
Either
机制根本不耦合到线程。因此,我们今天在现实生活中看到的是,许多异步编程解决方案都采用了Either
-style错误处理的变体。考虑以下任何链接中详细介绍的Javascript Promise:
Promise的概念允许您编写这样的异步代码(摘自最后一个链接):
var greetingPromise = sayHello();
greetingPromise
.then(addExclamation)
.then(function (greeting) {
console.log(greeting); // 'hello world!!!!’
}, function(error) {
console.error('uh oh: ', error); // 'uh oh: something bad happened’
});
基本上,promise是一个对象:
基本上,由于当跨多个线程进行计算时,语言的本机异常支持不起作用,因此promises实现必须提供错误处理机制,并且这些结果类似于Haskell的Maybe
/ Either
类型。
Haskell类型的系统将要求用户承认的可能性Nothing
,而编程语言通常不需要捕获异常。这意味着我们将在编译时知道用户已经检查了错误。
throws NPE
在每个单个签名和catch(...) {throw ...}
每个方法主体中添加一个。但我确实相信,与Maybe一样,检查市场也是如此:可空性是可选的,并在类型系统中进行跟踪。
可能单子与大多数主流语言对“空意味着错误”检查的使用基本相同(除了它需要检查空值),并且在很大程度上具有相同的优点和缺点。
Maybe
写两个数字a + b
而无需检查None
,结果又是一个可选值。
Maybe
类型仍然适用,但是Maybe
用作monad会添加语法糖,从而可以更优雅地表达null-ish逻辑。
异常处理可能是分解和测试的真正难题。我知道python提供了不错的“ with”语法,使您无需严格的“ try ... catch”块即可捕获异常。但是例如,在Java中,尝试catch块很大,很冗长,既冗长又极其冗长,并且很难分解。最重要的是,Java添加了围绕检查和未检查异常的所有干扰。
相反,如果您的monad捕获异常并将其视为Monadic空间的属性(而不是某些处理异常),那么您可以自由地混合和匹配绑定到该空间的函数,而不管它们抛出或捕获的内容是什么。
如果更好的是,您的monad阻止了可能发生异常的情况(例如将空检查强加到Maybe中),那就更好了。如果...那么,比尝试...捕获要容易得多,那么分解和测试就容易得多。
从我所看到的来看,Go通过指定每个函数返回(答案,错误)采取了类似的方法。这有点类似于将函数“提升”到monad空间,在该空间中核心答案类型装饰有错误指示,并有效地避免了引发和捕获异常的发生。