2

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()))
Aandal
  • 51
  • 2
  • 11

1 Answers1

2

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.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
  • @jwvh No, implicits are resolved also at compile time. – Dmytro Mitin May 06 '19 at 09:26
  • Yes, of course they are. My bad. Then why won't the compiler adjust `C` to `Fruit` which would comply with both restrictions? – jwvh May 06 '19 at 09:30
  • @jwvh `bananaBasket` is a `Basket[Banana]` so `A=Banana`. `newItems: List[B] = List(Orange())` so `B >: Orange` . So minimal `B, C` satisfying `[B >: A <: Fruit, C >: A]` are `B=Fruit, C=A=Banana` and this doesn't satisfy `ev: B <:< C`. Compile error says precisely that: `Error: Cannot prove that App.Fruit <:< App.Banana`. (I made `trait Fruit extends Product with Serializable` to avoid [1](https://typelevel.org/blog/2018/05/09/product-with-serializable.html)). – Dmytro Mitin May 06 '19 at 09:49
  • @jwvh Both type inference and implicit resolution occur at compile time but `A,B...` in `[A >: LA <: UA, B...]` are inferred before checking `(implicit ev: F[A, B] <:< G[A, B], ...)`. See examples `def tupleIfSubtype[T <: U, U]` and `def tupleIfSubtype[T, U](t: T, u: U)(implicit ev: T <:< U)` in https://blog.bruchez.name/2015/11/generalized-type-constraints-in-scala.html – Dmytro Mitin May 06 '19 at 09:58
  • @jwvh These were examples when types are inferred before implicits are resolved. And here is example vice versa when implicit is resolved before type is inferred: `trait Typeclass[T]` `implicit val int: Typeclass[Int] = null` `def foo[T]()(implicit ev: Typeclass[T]) = ???` `foo()` – Dmytro Mitin May 06 '19 at 10:04
  • @dmytromitin Thanks for the explanation. Now it is clear to me. Could you also please explain why you have used `F[A, B]` and `G[A, B]` in implicit ev? How should I read that and what do they mean? Isn't `B <:< A` a representation for `<:<[B, A]`? – Aandal May 07 '19 at 05:04
  • @Aandal Yes, `B <:< A` is `<:<[B, A]`. I meant that in `implicit ev` you can check that some type depending on `A, B` (for example it can be `B`) is a subtype of some other type depending on `A,B` (for example it can be `A`). – Dmytro Mitin May 07 '19 at 07:53