Search code examples
scalascala-cats

EitherT throws compilation error when returning child class


In the code below I'm using cats' EitherT class. It compiles, but I was forced to use the Error trait in:

def isNeg(i:Int): Future[Either[Error,Int]] = Future {

where my intent was to declare the function as:

def isNeg(i:Int): Future[Either[IsPositive,Int]] = Future {

but if I use IsPositive (the class returned in the function's Left) Scala throws a compilation error. Why doesn't it compile with IsPositive ?

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

trait Error
case class IsNegative() extends Error
case class IsPositive() extends Error

object HelloScala extends App {

    def isPos(i:Int): Future[Either[IsNegative,Int]] = Future {
      if (i>0)
          Right(i)
      else
          Left(IsNegative())
    }

    def isNeg(i:Int): Future[Either[Error,Int]] = Future {
      if (i<0)
          Right(i)
      else
          Left(IsPositive())
    }

    def myFunction (i: Int) = {
       val result = for {
          ret <- EitherT(isPos(i)) // <-- compilation error, if IsPositive is used in isNeg
          ret2 <- EitherT(isNeg(ret))
       } yield ret2
       println(result.value)
    }

}

Solution

  • How does yield works? It is translated to flatMap (see here). So, let's try to rewrite your case with flatMap and with explicit types for non-compilable version:

    val eithert1: EitherT[Future, IsNegative, Int] = EitherT(isPos(i))
    def eithert2(ret: Int): EitherT[Future, IsPositive, Int] = EitherT(isNeg(ret))
    eithert1.flatMap(ret => eithert2(ret)).map(ret2 => ret2)
    

    And now what types you have:

    // What is expected (see `EitherT` type bounds in sources, I copied here)
    // flatMap -> EitherT[F[_], A, B].flatMap[AA >: A, D](f: B => EitherT[F, AA, D])
    // what you have:
    // EitherT[Future, IsNegative, Int].flatMap[IsPositive >: IsNegative, Int](f: Int => EitherT[Future, IsPositive, Int])
    

    You see: IsPositive >: IsNegative, which is wrong. Expected super-type for IsNegative (either Error or IsNegative)