【问题标题】:Scala, ZIO - how to convert EitherT to ZIO?Scala,ZIO - 如何将 EitherT 转换为 ZIO?
【发布时间】:2021-07-17 15:25:46
【问题描述】:

我有一个简单的方法签名,返回类型为EitherT

def run(someEither: Either[SomeException, Statement], id: String): EitherT[Future, EncodingException, ResultSet]

但更高级别的方法有一个签名:

def someMethod(...) : zio.IO[SomeException, SomeResult]

我像这样在someMethod 中调用run 方法:

run(someEither, data.getOrElse("empty")).bimap(
      { e: SomeException => IO.fail(e) },
      _ => IO.succeed(entity)
    )

但我收到关于 Type mismatch 的编译错误 - 必需:IO,找到:EitherT。 如何将EitherT 转换为zio.IO

【问题讨论】:

    标签: scala scala-cats zio


    【解决方案1】:

    我认为你可以这样做:

    import zio._
    import cats.data.EitherT
    import scala.concurrent.Future
    
    type ResultSet = ???
    
    val eitherT: EitherT[Future, Exception, ResultSet] = ???
    val result: IO[Throwable, ResultSet] = ZIO.fromFuture(_ => eitherT.value).flatMap(ZIO.fromEither)
    

    【讨论】:

      【解决方案2】:

      这取决于您的数据是什么样的以及您希望如何处理失败的期货,但如果您不想处理来自未来的特定类型的 Throwable,这里有一些选项:

      // EitherT[Future, E, A] to IO[E, A]
      val io: IO[E, A] =
        ZIO.fromFuture(eitherT.value)
          .orDie
          .flatMap(ZIO.fromEither)
      
      // EitherT[Future, Throwable, A] to Task[A]
      val task: Task[A] =
        ZIO
          .fromFuture(_ => et.value)
          .flatMap(ZIO.fromEither)
      
      // EitherT[Future, Throwable, A] to UIO[A]
      val task: UIO[A] =
        ZIO
          .fromFuture(_ => et.value)
          .flatMap(ZIO.fromEither)
          .orDie
      

      更长的解释

      首先,没有用于从EitherT 转换的内置函数,因此您需要使用底层的Future,您可以从中快速获取ZIO Task

      val eitherT: EitherT[Future, A, B] = ???
      val underlying: Future[Either[A, B]] = eitherT.value
      
      // Note that this is equivalent to IO[Throwable, Either[...]]
      val zioTask: Task[Either[MyError, MyResult]] = ZIO.fromFuture(_ => underlying)
      
      // Or all at once:
      val zioTask = ZIO.fromFuture(_ => eitherT.value)
      

      我和 OP 有类似的需求,但就我而言,左派也不例外,而是我自己的 MyError

      val underlying: Future[Either[MyError, MyResult]]
      

      如果您也遇到类似情况,那么您需要决定如何处理可能在未来失败的任何异常。一种可能的选择是...

      忽略它们,如果它们发生,让纤维死亡

      如果将来的异常代表代码中的缺陷,那么通常没有其他选择,只能让光纤死掉,因为它无法继续进行。用orDie 杀死的光纤将有一个Throwable 代表原因,但您可以选择(我建议)使用您自己的异常,详细说明通过orDieWith 发生故障的方式和原因。

      val uio: UIO[Either[MyError, MyResult]] = task.orDie
      
      // Or
      
      case class ExplanatoryException(cause: Throwable)
        extends RuntimeException(
          "A failure was encountered when we used XYZ-lib/made service call to FooBar/etc",
          cause
        )
      val uio: UIO[Either[MyError, MyResult]] = task.orDieWith(ExplanatoryException)
      
      // Then finally
      
      val fullyConverted: IO[MyError, MyResult] = uio.flatMap(ZIO.fromEither)
      

      现在您可以使用干净的IO

      扩展方法!

      如果您经常这样做,您可以向EitherT 添加一个扩展方法来为您进行转换:

      implicit class EitherTOps[A, B](et: EitherT[Future, A, B]) {
        def toIO: IO[A, B] = ZIO.fromFuture(_ => et.value)
          .orDie
          .flatMap(ZIO.fromEither)
      
        def toIOWith(f: Throwable => Throwable): IO[A, B] = ZIO.fromFuture(_ => et.value)
          .orDieWith(f)
          .flatMap(ZIO.fromEither)
      }
      

      还有更多!

      如果您能够约束A <: Throwable,那么为EitherT 编写一些方便的toTask 扩展方法就很容易了,但我将把它作为练习留给读者。

      如果你先验知道你可能从 Future 得到什么样的Throwable,并且你想真正优雅地处理它,那么请参阅this question,它涵盖了所有的方法。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-10-22
        • 2021-07-21
        • 2021-08-04
        • 1970-01-01
        • 2020-11-01
        • 2022-12-01
        • 2019-06-16
        • 2020-01-30
        相关资源
        最近更新 更多