2
votes

I am learning the concept of type variance and bounds in scala and how to use them. I came across the below question on stack overflow where one of the solutions mentioned how to prevent scala from generalizing the types.

Covariant type parameter

Below is the code posted in the solution. In the below code, How does adding a new type parameter C help? I understand how B is constrained ( as a supertype of A and subtype of Fruit). But I am completely lost as to what C does here. Why should it be Super type of A. Why does the implicit evidence require B to be subtype of C?

And why there is an irrelevant error when adding List of Orange object that Fruit is not subtype of Banana. Can someone please explain this?

I am guessing to satisfy the first constraint, Orange object is inferred as Fruit object but lost after that as to why it says Fruit is not a subtype of Banana.

case class Banana() extends Fruit
defined class Banana

case class Orange() extends Fruit
defined class Orange

case class Basket[+A <: Fruit](items: List[A]) {
    // ...
    def addAll[B >: A <: Fruit, C >: A](newItems: List[B])(implicit ev: B <:< C): Basket[B] =
  new Basket(items ++ newItems)
    // ...
  }
defined class Basket

val bananaBasket: Basket[Banana] = Basket(List(Banana(), Banana()))
bananaBasket: Basket[Banana] = Basket(List(Banana(), Banana()))

bananaBasket.addAll(List(Orange())) // not accepted
Main.scala:593: Cannot prove that Product with Serializable with cmd27.Fruit <:< cmd47.Banana.
bananaBasket.addAll(List(Orange()))
1

1 Answers

2
votes

Syntax

def foo[A >: LA <: UA, B...](...)(implicit ev: F[A, B] <:< G[A, B], ...)

means that

  • firstly A, B ... should be inferred so that conditions A >: LA <: UA, B... are satisfied and
  • secondly for these inferred A, B ... conditions F[A, B] <:< G[A, B], ... should be checked.

Basically C >: A and ev: B <:< C mean (since C is used nowhere else and compiler is looking for the lowest upper bound for C) that C is A and for it we should check that B <:< A. Just we can't remove C >: A and replace ev: B <:< C with ev: B <:< A since then we'll have Error: covariant type A occurs in contravariant position in type B <:< A of value ev.

So we want B to be inferred so that B >: A <: Fruit (i.e. B should be a supertype of A) and for this B to check that B <:< A (i.e. B should be a subtype of A). So this can be satisfied only when A = B. And this prevents bananaBasket.addAll(List(Orange())) from compilation.