Why / How does this contravariant scala example work?

This code seems to create a contravariant example: FruitBox can accept apples or oranges.

class Fruit(name: String) { }
case class Apple(name: String) extends Fruit(name) {}
case class Orange(name: String) extends Fruit(name) {}

class Box[-T] {
  type U >: T
  def put(t: U): Unit= {box += t}
  val box = scala.collection.mutable.ListBuffer[U]()
}

  object test {
   val FruitBox = new Box[Fruit]
   // Fruit Box takes in everything
   FruitBox.put(new Fruit("f1"))
   FruitBox.put(new Orange("o1"))
   FruitBox.put(new Apple("a1")) 

   // and Orange Box takes in only oranges
    val OrangeBox = new Box[Orange]
     //OrangeBox.put(new Apple("o2")  ==> Compile Error that makes sense
    OrangeBox.put(new Orange("o2"))

   // Contra-variant nature is also demonstrated by
     val o: Box[Orange] = FruitBox 
  }

      

It's okay ... but why does it work? in particular: 1. When FruitBox is initialized, why doesn't "type U>: T" restrict it to FruitBox supertypes? Despite this limitation, FruitBox can supply subtypes if Fruit (oranges and apples) ... how?

+3


source to share


2 answers


First, while Scala allows you to write new Box[Fruit]

while leaving an U

abstract member, I don't understand why. However, Scala seems to be accepting U = T

in this case. Since your code never implements U

, you can simply replace it with T

. So, you get to def put(t: Fruit)

in FruitBox

: Of course, it takes Apple

s, as they Fruit

s!
The only thing Scala knows about U

is that it is a supertype of T

; thus, T

is a subtype U

, and each subtype is T

. Therefore, any subtype Fruit

can be passed to FruitBox.put

. So, is def put(t: U): Unit

actually the same as put(t: T): Unit

if you don't implement U

as in new Box[Fruit] { type U = Object }

.

FruitBox, which can hold apples and oranges, and OrangeBox, which can only hold oranges. This looks like conflicting behavior and I'm fine with it.



This is not at all contravariant behavior; you get the same with

class Box[T] {
  def put(t: T): Unit= {box += t}
  val box = scala.collection.mutable.ListBuffer[T]()
}

      

+3


source


In addition to @AlexanderRomanov's answer, Scala treats it U >: T

as an existential type _ >: T

. It basically materializes before T

when you actually specify it:

val orangeBox = new Box[Orange]

      

Btw, another common way to do this is to implement U

in a subclass:

trait Box[-T]{
  type U >: T
}

class BoxImpl[T] extends Box[T]{
  type U = T
}

      

So, if you used a type parameter instead of a type member, the code would look like this:



class Box[T, _ >: T]
val orangeBox = new Box[Orange, Orange]

      

In your case, Scala simply finds a suitable type for you to "materialize" this existential.

The only inconsistency here is that it doesn't actually allow:

class BoxImpl[-T] extends Box[T]{
  type U = T
}
//error: contravariant type T occurs in invariant position in type T of type U

      

So basically, when you create yours Box[Orange]

, it ignores -T

as in class BoxImpl[T] extends Box[T]

(whatever the original Box

had -T

)

0


source







All Articles