При понимании тип и смещение первого шага в цепочке определяет тип всех остальных шагов в цепочке. Поскольку 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)))