scalascala-catsmockito-scala

Scala type system, cannot find common ancestor inline


I am on an heavily typed system with some generic methods declared as def execute]C <: A#C](cmd: C):EitherT[Future, Fail, Seq[A#E]] (where A is a generic type on the class.

This works well. However in my tests, when I mock those calls, I have to explictiely type the super types of Fail and A#E or my code cannot compile.

// Event is the base type of Receiver#E
val event:Event = SubOfEvent()
handler.execute(any[SubOfCommand]) returns Seq(event).asRightT[Future, Fail]

val fail:Fail = SubOfFail()
handler.execute(any[SubOfCommand]) returns fail.asLeftT[Future, Seq[Receiver#E]]

If I inline the declarations of event or fail I have a Type mismatch:

  found   : cats.data.EitherT[scala.concurrent.Future,SubOfFail,scala.collection.immutable.Seq[SubOfEvent]]
  required: cats.data.EitherT[scala.concurrent.Future,Fail,scala.collection.immutable.Seq[Receiver#E]]
     (which expands to)  cats.data.EitherT[scala.concurrent.Future,Fail,scala.collection.immutable.Seq[Event]]
 Note: SubOfFail <: Fail, but class EitherT is invariant in type A.
 You may wish to define A as +A instead. (SLS 4.5)
     handler.execute(any[SubOfCommand]) returns SubOfFail().asLeftT[Future,
                                                                   ^

I understand the message about EitherT being invariant in type A. But I was expecting it to be able to translate EitherT[F, SubOfA, B] as EitherT[F, SubOfA.asInstanceOf[A], B].

Can someone help me to reveal the flaw in my reasoning ?

Thanks


Solution

  • But I was expecting it to be able to translate EitherT[F, SubOfA, B] as EitherT[F, SubOfA.asInstanceOf[A], B].

    Being invariant means exactly that it isn't possible to "translate" this way: an EitherT[F, SubOfA, B] simply isn't an EitherT[F, A, B].

    Now, a separate question is why the type for fail isn't inferred to be Fail despite an expected type for the whole fail.asLeftT[Future, Seq[Receiver#E]]. And the answer there is that Scala type inference just doesn't work that way; when typing expression.method(...), it will type expression first and can't make use of the expected return type of method.

    You can still write them inline, but you need type ascription:

    (SubOfFail(): Fail).asLeftT[Future, Seq[Receiver#E]]
    

    In case of Seq, explicit type parameter will work as well:

    Seq[Event](SubOfEvent()).asRightT[Future, Fail]