Try[Result]、IO[Result]、Either[Error,Result],我到底应该用哪个 [英] Try[Result], IO[Result], Either[Error,Result], which should I use in the end

查看:77
本文介绍了Try[Result]、IO[Result]、Either[Error,Result],我到底应该用哪个的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道我的方法的签名应该是什么,以便我优雅地处理不同类型的失败.

I'd like to know what should be the signature of my methods so that I handle different kind of failures elegantly.

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

This question is somehow the summary of many questions I already had about error handling in Scala. You can find some questions here:

现在,我明白以下几点:

For now, I understand the following:

  • 两者都可以用作可能失败的方法调用的结果包装器
  • Try 是一个 Right biaised任一,失败是一个非致命的例外
  • IO (scalaz) 有助于构建处理 IO 操作的纯方法
  • 所有 3 项都可以轻松用于理解
  • 由于不兼容的 flatMap 方法,所有 3 种方法都不容易在理解中混合使用
  • 在函数式语言中,我们通常不会抛出异常,除非它们是致命的
  • 对于真正异常的情况,我们应该抛出异常.我想这是尝试的方法
  • 创建 Throwables 会对 JVM 造成性能损失,并且不打算用于业务流控制

知识库层

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

Now please consider that I have a UserRepository. The UserRepository stores the users and defines a findById method. The following failures could happen:

  • 致命故障(OutOfMemoryError)
  • IO 失败,因为数据库不可访问/可读

此外,用户可能会丢失,导致 Option[User] 结果

Additionally, the user could be missing, leading to an Option[User] result

使用存储库的 JDBC 实现,可以抛出 SQL、非致命异常(违反约束或其他),因此使用 Try 是有意义的.

Using a JDBC implementation of the repository, SQL, non-fatal exceptions (constraint violation or others) can be thrown so it can make sense to use Try.

当我们处理 IO 操作时,如果我们想要纯函数,那么 IO monad 也是有意义的.

As we are dealing with IO operations, then the IO monad also makes sense if we want pure functions.

所以结果类型可能是:

  • 尝试[选项[用户]]
  • IO[Option[User]]
  • 还有别的吗?

服务层

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

Now let's introduce a business layer, UserService, which provides some method updateUserName(id,newUserName) that uses the previously defined findById of the repository.

可能会发生以下故障:

  • 所有存储库故障都传播到服务层
  • 业务错误:无法更新不存在的用户的用户名
  • 业务错误:新用户名太短

那么结果类型可以是:

  • 尝试[Either[BusinessError,User]]
  • IO[Either[BusinessError,User]]
  • 还有别的吗?

此处的 BusinessError 不是 Throwable,因为它不是异常失败.

BusinessError here is not a Throwable because it is not an exceptional failure.

使用 for-comprehensions

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

I would like to keep using for-comprehensions to combine method calls.

我们不能轻易地在 for-comprehension 上混合不同的 monad,所以我想我应该为我的所有操作提供某种统一的返回类型,对吗?

We can't easily mix different monads on a for-comprehension, so I guess I should have some kind of uniform return type for all my operations right?

我只是想知道在现实世界的 Scala 应用程序中,当可能发生不同类型的故障时,您如何成功地继续使用 for-comprehensions.

I just wonder how do you succeed, in your real world Scala applications, to keep using for-comprehensions when different kind of failures can happen.

就目前而言,for-comprehension 对我来说很好用,使用的服务和存储库都返回 Either[Error,Result] 但所有不同类型的失败都融合在一起,变得有点麻烦处理这些故障.

For now, for-comprehension works fine for me, using services and repositories which all return Either[Error,Result] but all different kind of failures are melted together and it becomes kind of hacky to handle these failures.

您是否定义了不同类型的 monad 之间的隐式转换,以便能够使用 for-comprehensions?

Do you define implicit conversions between different kind of monads to be able to use for-comprehensions?

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

Do you define your own monads to handle failures?

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

By the way perhaps I'll be using an asynchronous IO driver soon. So I guess my return type could be even more complicated: IO[Future[Either[BusinessError,User]]]

欢迎任何建议,因为我真的不知道要使用什么,而我的应用程序并不花哨:它只是一个 API,我应该能够区分可以向客户端显示的业务错误方面,和技术错误.我试图找到一个优雅而纯粹的解决方案.

Any advice would be welcome because I don't really know what to use, while my application is not fancy: it is just an API where I should be able to make a distinction between business errors that can be shown to the client side, and technical errors. I try to find an elegant and pure solution.

推荐答案

这就是 Scalaz 的 EitherT monad 转换器的用途.一堆IO[Either[E, A]]等价于EitherT[IO, E, A],只不过前者必须作为多个monad依次处理,而后者自动是单个 monad,它向基本 monad IO 添加 Either 功能.您同样可以使用 EitherT[Future, E, A] 为异步操作添加非异常错误处理.

This is what Scalaz's EitherT monad transformer is for. A stack of IO[Either[E, A]] is equivalent to EitherT[IO, E, A], except that the former must be handled as multiple monads in sequence, whereas the latter is automatically a single monad that adds Either capabilities to the base monad IO. You can likewise use EitherT[Future, E, A] to add non-exceptional error handling to asynchronous operations.

一般来说,Monad 转换器可以解决在单个 for 理解和/或 monadic 操作中混合多个 monad 的需求.

Monad transformers in general are the answer to a need to mix multiple monads in a single for-comprehension and/or monadic operation.

我假设您使用的是 Scalaz 7.0.0 版.

I will assume you are using Scalaz version 7.0.0.

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

In order to use the EitherT monad transformer on top of the IO monad, you first need to import the relevant parts of Scalaz:

import scalaz._, scalaz.effect._

您还需要定义您的错误类型:RepositoryErrorBusinessError 等.这照常工作.您只需要确保您可以,例如,将任何 RepositoryError 转换为 BusinessError,然后进行模式匹配以恢复错误的确切类型.

You also need to define your error types: RepositoryError, BusinessError, etc. This works as usual. You just need to make sure that you can, e.g., convert any RepositoryError into a BusinessError and then pattern match to recover the exact type of error.

然后你的方法的签名变成:

Then the signatures of your methods become:

def findById(id: ID): EitherT[IO, RepositoryError, User]
def updateUserName(id: ID, newUserName: String): EitherT[IO, BusinessError, User]

在您的每个方法中,您可以使用基于 EitherT-and-IO 的 monad 堆栈作为单个统一的 monad,可在 for-像往常一样理解.EitherT 将负责在整个计算过程中处理基本 monad(在本例中为 IO),同时也以 Either 通常的方式处理错误(默认情况下已经是右偏的除外,因此您不必经常处理所有常见的 .right 垃圾).当你想做一个 IO 操作时,你所要做的就是使用 IO 上的 liftIO 实例方法将它提升到组合的 monad 堆栈中>.

Within each of your methods, you can use the EitherT-and-IO-based monad stack as a single, unified monad, available in for-comprehensions as usual. EitherT will take care of threading the base monad (in this case IO) through the whole computation, while also handling errors the way Either usually does (except already right-biased by default, so you don't have to constantly deal with all the usual .right junk). When you want to do an IO operation, all you have to do is raise it into the combined monad stack by using the liftIO instance method on IO.

附带说明一下,当以这种方式工作时,EitherT 伴随对象中的函数可能非常有用.

As a side note, when working this way, the functions in the EitherT companion object can be very useful.

这篇关于Try[Result]、IO[Result]、Either[Error,Result],我到底应该用哪个的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆