1

Consider the below:

trait AA {
    def children: List[AA]
}

trait BB {
    def children: List[BB]
}

class CC extends AA, BB {
    override def children: List[AA] & List[BB] = ???
}

When we override children in CC, the overridden method is merged entity of the top-level methods. And hence the return type List[AA] & List[BB] makes sense.

What I don't understand is, how does the below compile?

class DD extends AA, BB {
    override def children: List[AA & BB] = ???
}

List is co-variant, hence (here is the source for proof):

List[AA & BB] <: List[AA] & List[BB]

DD can only compile if also List[AA] & List[BB] <: List[AA & BB]. Is it true? And if so, then isn't List[AA] & List[BB] =:= List[AA & BB]. Please suggest


It appears to me that List[AA & BB] =:= List[AA] & List[BB]. Consider this:

    val xx: List[AA & BB] = ???
    val yy: List[AA] & List[BB] = ???
    
    val z1: List[AA] & List[BB] = xx
    val z2: List[AA & BB] = yy
Jatin
  • 31,116
  • 15
  • 98
  • 163

2 Answers2

5

You write that for DD to compile, List[AA] & List[BB] must be a subtype of List[AA & BB]. I don't see why you think that, and you're in fact mistaken. Method return types are covariant, and hence it's the other way around: List[AA & BB] must be a subtype of List[AA] & List[BB]. And this is indeed the case, so the code is fine.

Matthias Berndt
  • 4,387
  • 1
  • 11
  • 25
4

Matthias Berndt already answered that List[AA] & List[BB] <: List[AA & BB] is not needed for your code to compile. However, a separate point is that it is in fact true (and so is List[AA] & List[BB] =:= List[AA & BB]). Why?

  1. Consider a value x of type List[AA] & List[BB].
  2. It must be both a List[AA] and List[BB].
  3. That is, both a list of AAs and a list of BBs.
  4. So any element y of this list must be an AA because x is a list of AAs, and a BB because x is a list of BBs.
  5. So y must be an AA & BB.
  6. Since every element of x has type AA & BB, x has type List[AA & BB].

This reasoning is specific to List, but it generalizes and not just to covariant types:

If C is a type constructor, then C[A] & C[B] can be simplified using the following three rules:

  • If C is covariant, C[A] & C[B] ~> C[A & B]
  • If C is contravariant, C[A] & C[B] ~> C[A | B]
  • If C is non-variant, emit a compile error
Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487