Let
val foos: List[Foo[_]] = ???
This is the same as
val foos: List[Foo[T] forSome { type T }] = ???
This makes foos
a list of pairs. Each pair consists of a type T
and a value of type Foo[T]
. In the foreach
, destructure the pair to get access to that type and name it, and also get the value:
foos.foreach { case foo: Foo[t] =>
val x = foo.bar1() // inferred type t
foo.bar2(x) // foo.bar2 wants a t, x is a t, this is well-formed
}
Scastie
Note: the "pairs" vanish after type erasure. In the erased code, the List
just contains Foo
objects. It is necessary to explicitly destructure the pair with a case
. If you do not, then foo
will refer to the entire pair, and at each use the compiler will implicitly destructure it. When this happens, the two appearances of the contained type are not considered equal.
foos.foreach { foo =>
val x = foo.bar1()
foo.bar2(x)
}
is about the same as
foos.foreach { foo =>
val x = foo match { case foo0: Foo[t] => foo0.bar1() }
foo match { case foo0: Foo[u] => foo0.bar2(x) }
}
The compiler doesn't see that the t
and the u
are actually the same. This is because t
is not in scope when x
is defined, so you end up with x: Any
, and thus you've lost type information.
Despite the existential type, this will work in future Scala (with the _
syntax).