Scala 试试[Result],IO[Result],或者[Error,Result],我最后应该使用哪一个

Scala 试试[Result],IO[Result],或者[Error,Result],我最后应该使用哪一个,scala,scalaz,either,Scala,Scalaz,Either,我想知道我的方法的特征应该是什么,这样我才能优雅地处理不同类型的失败 这个问题是我对Scala中错误处理的许多问题的总结。您可以在这里找到一些问题: 目前,我理解如下: 两者都可以用作可能失败的方法调用的结果包装器 Try是一种右倾偏见,失败是一种非致命的例外 IO(scalaz)帮助构建处理IO操作的纯方法 所有这三个都很容易在一个简单的环境中使用,以便于理解 由于不兼容的flatMap方法,这三种方法不容易在一个可理解的环境中混合使用 在函数语言中,除非异常是致命的,否则通常不

我想知道我的方法的特征应该是什么,这样我才能优雅地处理不同类型的失败

这个问题是我对Scala中错误处理的许多问题的总结。您可以在这里找到一些问题:


目前,我理解如下:

  • 两者都可以用作可能失败的方法调用的结果包装器
  • Try是一种右倾偏见,失败是一种非致命的例外
  • IO(scalaz)帮助构建处理IO操作的纯方法
  • 所有这三个都很容易在一个简单的环境中使用,以便于理解
  • 由于不兼容的flatMap方法,这三种方法不容易在一个可理解的环境中混合使用
  • 在函数语言中,除非异常是致命的,否则通常不会抛出异常
  • 对于真正的异常情况,我们应该抛出异常。我想这就是尝试的方法
  • 创建一次性文件会给JVM带来性能成本,并且不会用于业务流控制

存储库层

现在请考虑我有一个<代码> UserRepository <代码>。
UserRepository
存储用户并定义
findById
方法。可能发生以下故障:

  • 致命故障(
    OutOfMemoryError
  • IO失败,因为数据库不可访问/不可读
此外,用户可能丢失,导致出现
选项[user]
结果

使用存储库的JDBC实现,可以抛出SQL、非致命异常(约束冲突或其他异常),以便使用Try

当我们处理IO操作时,如果我们想要纯函数,IO单子也是有意义的

因此,结果类型可以是:

  • 试试[选项[用户]]
  • IO[选项[用户]]
  • 还有别的吗

服务层

现在,让我们介绍一个业务层,
UserService
,它提供了一些方法
updateUserName(id,newUserName)
,该方法使用存储库先前定义的
findById

可能发生以下故障:

  • 所有存储库故障都会传播到服务层
  • 业务错误:无法更新不存在的用户的用户名
  • 业务错误:新用户名太短
那么结果类型可以是:

  • 试试[业务错误,用户]]
  • IO[业务错误,用户]]
  • 还有别的吗
这里的BusinessError不是一次性的,因为它不是一个例外的失败


用于理解

我想继续使用for-comprehension来组合方法调用

我们不能简单地在一个表上混合不同的单子来理解,所以我想我的所有操作都应该有某种统一的返回类型,对吗

我只是想知道,在现实世界的Scala应用程序中,当不同类型的失败可能发生时,如何成功地继续使用它来理解

现在,For comprehension对我来说很好,它使用的服务和存储库都返回
或者[Error,Result]
,但是所有不同类型的故障都融合在一起,处理这些故障变得有点麻烦

您是否定义了不同种类的单子之间的隐式转换以用于理解

您是否定义自己的monad来处理故障

顺便说一下,我可能很快就会使用异步IO驱动程序了。 所以我想我的返回类型可能更复杂:
IO[Future[nother[BusinessError,User]]]



任何建议都是受欢迎的,因为我真的不知道该使用什么,而我的应用程序并不新奇:它只是一个API,在这里我应该能够区分可以显示给客户端的业务错误和技术错误。我试图找到一个优雅而纯净的解决方案。

这就是Scalaz的
EitherT
monad transformer的用途。一堆
IO[E,A]]
相当于
EitherT[IO,E,A]
,只是前者必须按顺序作为多个单子处理,而后者则自动成为单个单子,向基本单子
IO
添加
or
功能。同样,您可以使用
EitherT[Future,E,A]
将非异常错误处理添加到异步操作中

一般来说,单声道转换器可以满足在一个
中混合多个单声道以进行理解和/或单声道操作的需要


编辑:

我假设您使用的是Scalaz 7.0.0版

要在
IO
monad上使用
EitherT
monad转换器,首先需要导入Scalaz的相关部分:

import scalaz._, scalaz.effect._
您还需要定义错误类型:
RepositoryError
BusinessError
,等等。这一切正常。例如,您只需确保可以将任何
RepositoryError
转换为
BusinessError
,然后进行模式匹配以恢复错误的确切类型

然后,您的方法的签名变成:

def findById(id: ID): EitherT[IO, RepositoryError, User]
def updateUserName(id: ID, newUserName: String): EitherT[IO, BusinessError, User]
在每个方法中,您都可以使用基于
EitherT
-和-
IO
-的单子堆栈作为一个单一的、统一的单子,在
中可以像往常一样进行理解
EitherT
将负责在整个计算过程中处理基本monad(在本例中为
IO
),同时也会像
通常那样处理错误(默认情况下已经右偏的情况除外,因此您不必经常处理所有常见的
.right
垃圾)。当您想要执行
IO