Say I have a class named Box
with a type parameter, and it has the following utility methods:
class Box[T]
object Box {
def build() : Box[_] = ???
def combine(boxes: Set[Box[_]]) : Unit = ???
}
I'm trying to use these methods in several ways. Some will compile, some will not:
// 1
Box.combine(Set(Box.build())) // compiles
// 2
val boxes : Set[Box[_]] = Set(Box.build())
Box.combine(boxes) // compiles
// 3
val boxes2 = Set(Box.build())
Box.combine(boxes2) // compile error - "type mismatch... you may wish to investigate a wildcard type as `_ <: Box`"
If T
is co-variant then everything compiles.
What's happening here? Why does that one way not compile? I'm guessing it has something to do with the implicit cast, no?
scala> val boxes2 = Set(Box.build())
boxes2: scala.collection.immutable.Set[Box[_$1]] forSome { type _$1 } = Set(Box$$anon$1@70e0accd)
"""<console>:14: warning: inferred existential type
scala.collection.immutable.Set[Box[_$1]] forSome { type _$1 }, which
cannot be expressed by wildcards, should be enabled
by making the implicit value scala.language.existentials visible.
This can be achieved by adding the import clause 'import
scala.language.existentials'
or by setting the compiler option -language:existentials."""
scala> Box.combine(boxes2)
"""<console>:16: error: type mismatch;
found : scala.collection.immutable.Set[Box[_$1]] where type _$1
required: Set[Box[_]]"""
scala> val boxes3: Set[Box[_]] = Set(Box.build())
scala> Box.combine(boxes3) // OK
The compiler infers the existential type to the outer type, so the result object has a type that doesn't fit combine
def check[A,B](implicit ev: A =:= B) = true
check[Set[Box[_]], Set[Box[t] forSome { type t }]] // true
check[Set[Box[_]], Set[Box[t]] forSome { type t }] // ERROR
as Set[Box[t] forSome { type t }]
is different from Set[Box[t]] forSome { type t }
However, type variance also plays a role here:
def f: Box[_] = Box[Int]() // Box is invariant in its type arg
def g: List[_] = List[Int]() // List is covariant in its type arg
f // Box[_] = Box()
g // List[Any] = List()
Set(f,f) // ERROR: type mismatch (as Set is invariant)
Set(g,g) // OK: scala.collection.immutable.Set[List[Any]] = Set(List())
List(f,f) // List[Box[_ >: _$1 with _$1]] forSome { type _$1; type _$1 } = List(Box(), Box())