Type arguments [Apple] do not conform to class BoxB's type parameter bounds [-F <: JapanApple]


#1

Following the article https://dzone.com/articles/scala-type-system-in-depth. Writing down with below code

abstract class Fruit
class Apple extends Fruit
class JapanApple extends Apple
class Orange extends Fruit

class BoxB[-F <: JapanApple](f: F) {
  def fruit: JapanApple = f
}

object App {
  def main(args: Array[String]) {
    val boxb: BoxB[JapanApple] = new BoxB[Apple](new Apple)
  }
}

But compiler complains error: type arguments [Apple] do not conform to class BoxB's type parameter bounds [-F <: JapanApple]. I couldn’t understand the error message because the signature of BoxB conforms to contravariant’s constraint, doesn’t it? But apparently I am wrong but I can’t figure it out why.


#2

-F <: JapanApple means that F has to be a subtype of JapanApple. But Apple is a supertype of JapanApple. The fact that F is contravariant doesn’t switch around the meaning of <:.


#4

No, the contravariance would make BoxB[Apple] a subtype of BoxB[JapanApple] if the type bound wasn’t there, so it would matter in this case.
But without the type bound, the fruit method doesn’t compile, because of its return type, which requires f: F to be a JapanApple.

With things that store something of their parameterized type, you usually want covariance (see e.g. the collection classes like immutable.List), because contravariance means you can’t use the type parameter as a return type.


#5

With the article - https://blog.codecentric.de/en/2015/04/the-scala-type-system-parameterized-types-and-variances-part-2/

  • Workaround to use a contravariant type in a return type:

    abstract class Box[-A] { def foo[B <: A](): B } is allowed.

I can fix with the code

class BoxB[-F](f: F) {

  def fruit[U <: F]: U = f.asInstanceOf[U]

}

But don’t understand very well the reason (I can get the sense of A <: B so M[B] <: M[A] but can’t bridge the missing gap that I am not aware of). Are there any more concrete examples that can help me understand this? I found I have a hard time understanding as it’s a bit too abstract to me. But with the example (for List[+A]), for instance, originally we have a list of Banana List(Banana, Banana), so it’s a list of Banana List[Banana]. Now the list adds another kind of fruit, say, Apple. In order to describe the list, it’s more appropriate to describe the list with List[Fruit] because the list is no longer just a list of Banana.

Thanks


#6

Your code with asInstanceOf will compile, but it will throw an exception when you try to use the fruit method on your boxb instance in your first example. So the reason this works is because you tell the compiler to ignore the type of f. So the workaround isn’t helping in this case.

I looked at the blog post you reffered to in your first post, its example for contravariance is wrong (doesn’t compile), and I’m not sure I understand what the error is or if it is simply a bad example. As I said before, storing stuff for later retrieving is usually covariant (for immutable containers at least), so anything with Box in its name is probably not good for explaining contravariance.

A better example might be some serialization class: let’s say you have a Printer class, that has a method to print objects of its parameter type:

abstract class Printer[-A] {
    def print(a: A): Unit
}

You may have an instance, that can print instances of JapanApple and one that can print instances of Fruit. So for Printer[JapanApple] the print method would accept only JapanApple objects, while for the Printer[Fruit] it would accept any Fruit. This means, a Printer[Fruit] can be used in place of a Printer[JapanApple] in any situation, but not the other way round. That’s the reason why a contravariant type may be used as a method parameter.

I recommend the official scala tour article on variance for an extended version of that example.


#7

Super short in addition to what the others have said: so you need >: instead of <:.

Does provide you an aha erlebnis yet?