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,请你帮我理解一下:
- 如果检测到
Left
,我如何避免执行对 load
(在 loadAll
中)的后续调用?
- 如果对
load
的调用成功,我如何将其 right
值用于以下对 load
的调用?
- 这是正确的做法吗?您会以不同的方式实施它吗?
谢谢大家
让我先贴出我认为可以满足您需求的代码,以及通常如何处理此类问题,然后我将描述是什么和为什么,也许还有一些其他建议:
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/
您可能还需要考虑使用此模式对性能的影响
我正在采用 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,请你帮我理解一下:
- 如果检测到
Left
,我如何避免执行对load
(在loadAll
中)的后续调用? - 如果对
load
的调用成功,我如何将其right
值用于以下对load
的调用? - 这是正确的做法吗?您会以不同的方式实施它吗?
谢谢大家
让我先贴出我认为可以满足您需求的代码,以及通常如何处理此类问题,然后我将描述是什么和为什么,也许还有一些其他建议:
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/
您可能还需要考虑使用此模式对性能的影响