Search code examples
scalagenericstype-conversioncovariancetype-parameter

Scala: compile error with wildcards (type parameters) and collections: "you may wish to investigate a wildcard type as"


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?


Solution

  • 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())