简体   繁体   English

Scala 与猫 IO/Either 而不是 Future/Exceptions

[英]Scala with cats IO/Either instead of Future/Exceptions

I'm in the process of adopting IO / Either to replace Future / Exception 's where applicable, but I need help with the following code:我正在采用IO / Either来替换适用的Future / Exception ,但我需要以下代码的帮助:

// some Java library
def dbLoad(id: Int): Int = {
  throw new Exception("db exception")
}

// my scala code
sealed trait DbError extends Exception with Product

object DbError {
  case object SomeError extends DbError
}

val load: Int => IO[Either[DbError, Int]] = { id =>
  IO.fromFuture { IO { Future {
    try { Right(dbLoad(id)) } catch { case NonFatal(e) => Left(SomeError) }
  } } }
}

val loadAll: IO[Either[DbError, (Int, Int, Int)]] =
  for {
    i1 <- load(1)
    i2 <- // call 'load' passing i1 as parameter, if i1 is 'right'
    i3 <- // call 'load' passing i2 as parameter, if i2 is 'right'
  } yield (i1, i2, i3) match {
    case (Right(i1), Right(i2), Right(i3)) => Right((i1, i2, i3))
    case _ => Left(SomeError)
  }

I have not been able to get it working/compiling correctly, could you please help me understand:我无法使其正常工作/编译,请您帮我理解:

  1. how can I avoid executing subsequent calls to load (in loadAll ) if a Left is detected?如果检测到Left如何避免执行对load (在loadAll )的后续调用?
  2. if a call to load is successful, how can I use its right value for the following call to load ?如果对load的调用成功,我如何在接下来的load调用中使用它的right值?
  3. is this the right approach to do it?这是正确的方法吗? Would you implement it in a different way?你会以不同的方式实现它吗?

Thanks everyone谢谢大家

Let me first put up the code that I think gets you what you want, and how typically things like this might be approached, I'll then describe what and why, and maybe some other suggestions:让我首先列出我认为可以满足您的需求的代码,以及处理此类事情的典型方式,然后我将描述内容和原因,也许还有其他一些建议:

import cats.data.EitherT
import cats.effect.IO
import cats.implicits._
import com.example.StackOverflow.DbError.SomeError

import scala.concurrent.Future
import scala.util.control.NonFatal
import scala.concurrent.ExecutionContext.Implicits.global

object StackOverflow {
  // some Java library
  def dbLoad(id: Int): Int = {
    throw new Exception("db exception")
  }

  // my scala code
  sealed trait DbError extends Exception with Product

  object DbError {
    case object SomeError extends DbError
  }

  val load: Int => IO[Either[DbError, Int]] = { id =>
    IO.fromFuture(
      IO(
        Future(dbLoad(id))
          .map(Right(_))
          .recover {
            case NonFatal(_) => Left(SomeError)
          }
      )
    )
  }

  val loadAll: EitherT[IO, DbError, (Int, Int, Int)] =
    for {
      i1 <- EitherT(load(1))
      i2 <- EitherT(load(i1))
      i3 <- EitherT(load(i2))
    } yield (i1, i2, i3)

  val x: IO[Either[DbError, (Int, Int, Int)]] = loadAll.value
}

First, rather than try-catch inside the IO[Future[_]] , Future itself has a number of combinators that can help you manage errors, assuming you have some control over what you get back.首先,与IO[Future[_]] try-catch 不同,Future 本身有许多组合器可以帮助您管理错误,假设您对返回的内容有一定的控制权。

For-comprehensions in Scala when applied in this fashion "short circuit" so if the first call to load(1) fails with a left then the rest of the comprehension won't execute.当以这种“短路”方式应用 Scala 中的For-comprehensions时,如果第一次调用load(1)失败并出现左值,那么其余的推导式将不会执行。 The use of EitherT allows you to manage the fact that your Either is "wrapped" in an effect type.使用EitherT可以让您管理您的Either被“包裹”在效果类型中的事实。

There are some problems with this approach, specifically around variance, you can read about them here:这种方法存在一些问题,特别是在方差方面,您可以在此处阅读有关它们的信息:

http://www.beyondthelines.net/programming/the-problem-with-eithert/ http://www.beyondthelines.net/programming/the-problem-with-eithert/

There are also some performance implications for using this pattern that you may want to consider使用这种模式还有一些性能影响,您可能需要考虑

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM