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

Scala with cats IO/Either instead of Future/Exceptions

我正在采用 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)
  }

我没能正确理解working/compiling,请你帮我理解一下:

  1. 如果检测到 Left,我如何避免执行对 load(在 loadAll 中)的后续调用?
  2. 如果对 load 的调用成功,我如何将其 right 值用于以下对 load 的调用?
  3. 这是正确的做法吗?您会以不同的方式实施它吗?

谢谢大家

让我先贴出我认为可以满足您需求的代码,以及通常如何处理此类问题,然后我将描述是什么和为什么,也许还有一些其他建议:

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

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

object Whosebug {
  // 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
}

首先,与 IO[Future[_]] 中的 try-catch 不同,Future 本身有许多组合器可以帮助您管理错误,前提是您可以控制返回的内容。

For-comprehensions 在 Scala 中以这种方式应用时会“短路”,因此如果对 load(1) 的第一次调用因左键而失败,则其余的理解将不会执行。 EitherT 的使用允许您管理 Either 被“包装”在效果类型中的事实。

这种方法存在一些问题,特别是方差方面的问题,您可以在这里阅读:

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

您可能还需要考虑使用此模式对性能的影响