EitherT с несколькими типами возврата - PullRequest
2 голосов
/ 07 января 2020

Я пытаюсь составить фьючерсы с целью понимания и EitherT, но у меня возникают проблемы из-за типов возвращаемых данных. Кто-нибудь может объяснить, почему это не компилируется и как я могу заставить его компилировать, изменяя понимание?

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

import cats.data.EitherT
import cats.implicits._

object CatsApp extends App {

  case class L1(a: String)
  case class L2(a: String)
  case class L3(a: String)

  case class R1(num: Int)
  case class R2(num: Int)
  case class R3(num: Int)

  def func1: Future[Either[L1, R1]] = {
    if (true) Future(Right(R1(1)))
    else Future(Left(L1("A")))
  }

  def func2: Future[Either[L2, R2]] = {
    if (true) Future(Right(R2(1)))
    else Future(Left(L2("A")))
  }

  def func3(a: R1, b: R2): Future[Either[L3, R3]] = {
    if (true) Future(Right(R3(a.num + b.num)))
    else Future(Left(L3("A")))
  }

  def comp = {
    for {
      f1 <- EitherT(func1)
      f2 <- EitherT(func2)
      f3 <- EitherT(func3(f1, f2))
    } yield f3
  }
}

1 Ответ

3 голосов
/ 07 января 2020

При понимании тип и смещение первого шага в цепочке определяет тип всех остальных шагов в цепочке. Поскольку Either смещено вправо, мы можем изменить только правильный тип между шагами для понимания, как советовал @Krzysztof. Например,

val e1: Either[String, Int] = Right(42)
val e2: Either[String, Char] = Right('A')

for {
  num  <- e1
  char <- e2
} yield "I compile despite having different Rights"

В вашем случае тип первого шага EitherT(func1) равен EitherT[Future, L1, R1], поэтому следующие шаги EitherT(func2) и EitherT(func3(f1, f2)) должны иметь следующую форму типа

EitherT[Future, L1, X]

, где может изменяться только X. Один из способов порадовать вас за понимание - это создать алгебра c типов данных из L с подобными

  sealed abstract class L(val a: String)
  final case class L1(s: String) extends L(s)
  final case class L2(s: String) extends L(s)
  final case class L3(s: String) extends L(s)

Вот рабочий пример

object CatsApp extends App {

  sealed abstract class L(val a: String)
  final case class L1(s: String) extends L(s)
  final case class L2(s: String) extends L(s)
  final case class L3(s: String) extends L(s)

  case class R1(num: Int)
  case class R2(num: Int)
  case class R3(num: Int)

  def func1: Future[Either[L, R1]] = {
    if (true) Future(Right(R1(1)))
    else Future(Left(L1("A")))
  }

  def func2: Future[Either[L, R2]] = {
    if (true) Future(Right(R2(1)))
    else Future(Left(L2("A")))
  }

  def func3(a: R1, b: R2): Future[Either[L, R3]] = {
    if (true) Future(Right(R3(a.num + b.num)))
    else Future(Left(L3("A")))
  }

  def comp: EitherT[Future, L, R3] = {
    for {
      f1 <- EitherT(func1)
      f2 <- EitherT(func2)
      f3 <- EitherT(func3(f1, f2))
    } yield f3
  }

  comp.value.andThen(v => println(v))
}

который выводит

Success(Right(R3(2)))
...