Reputation: 823
I'm trying to create a generic type and putting them into a list. Here's my code.
class A
class B extends A
class C extends B
class G[T <: B] {
def x(t: T) = {}
}
val g1 = new G[B]
val g2 = new G[C]
//val list1: List[G[B]] = List(g1, g2) -- this would not compile.
val list: List[G[_ <: B]] = List(g1, g2)
list.head.x()//now what ?
I was expecting type for x would be B but instead compiler complains about
_$1 where type _$1 <: com.example.cake.modules.robotics.B
list.head.x(new B)
Quick googling the problem pointed to me to the wild card (_ <: B). however not sure how to fix.
Upvotes: 3
Views: 4632
Reputation: 2866
the class G
uses type T
in a contravariant position (as x
's argument).
depending on your use case, you can model your class hierarchy such that passing a subclass of T
to x should work. this means your example code could look something like:
class A
class B extends A
class C extends B
class G[-T <: B] {
def x(t: T) = {}
}
val g1 = new G[B]
val g2 = new G[C]
val list: List[G[C]] = List(g1, g2) //compiles.
list.head.x(new C)
as said before, class G
uses type T
in a contravariant position.
so, if you want (as shown in your sample code) to be covariant, and not be forced to use contravariace, all you have to do, is make sure you only use type T
in "covariant places", e.g:
class A
class B extends A
class C extends B
class G[+T <: B] {
def x[R >: T <: B](r: R) = {}
}
val g1 = new G[B]
val g2 = new G[C]
val list: List[G[B]] = List(g1, g2) //compiles.
list.head.x(new B)
note the weird type constraint on method x: R >: T <: B
could be written as only: R >: T
which means any supertype of T
, i.e. you could also get:
list.head.x(new A)
to compile, and this is why you'll need the constraint on R
also being a subtype of B
Upvotes: 3
Reputation: 8534
For the reasons described in the section below, when you put the elements in the list you lose information on the precise type of g1
and g2
. This is not the case with shapeless HLists:
> import shapeless._
> val list = g1 :: g2 :: HNil
list: G[B] :: G[C] :: HNil = ::(cmd7$G@3a94d716, ::(cmd7$G@64992713, HNil))
> list.head.x(new B)
// successful
What the compiler is saying is, in fact, perfectly sensible (albeit somewhat obscure): note that g2
's type is G[C]
and its x
member accepts only a C
(or any subtype) as a parameter.
In fact:
scala> :type g2
G[C]
scala> g2.x(new C)
scala> g2.x(new B)
<console>:16: error: type mismatch;
found : B
required: C
g2.x(new B)
^
When you add both g1
and g2
you lose the precise type of both and end up, for the element type, with something that needs to be safe regardless of the element you get out. List[G[_ <: B]]
means that for every element of the list, T
in G[T]
needs to be T <: B
and also that this is the only guarantee for elements you take out of the list. So, in the end, what you get out with list.head
is a G[T <: B]
and thus T
may be any subtype of B
(which is an infinite set) and thus, if it is, for example, T = C
then x
would only accept values that are subtypes of C
as its t
argument. This makes it effectively impossible to provide a value for t
as T
may only be Nothing
(the bottom, inhabited type which has no value).
In fact, if you let scalac infer a type for list
, you get:
scala> val list = List(g1, g2)
list: List[G[_ >: C <: B]] = List(G@379619aa, G@cac736f)
which says that T
in G[T]
must be a subtype of B
and a supertype of C
, this puts a lower bound to T
and thus permits the call list.head.x(new C)
but not list.head.x(new B)
:
scala> list.head.x(new B)
<console>:18: error: type mismatch;
found : B
required: _2 where type _2 >: C <: B
list.head.x(new B)
It's important to note that the exact same would have happened if, instead of using head
you grabbed the second element of the list list(1)
.
To figure out what should be done here, we may need to know more of what you're trying to achieve as there's no trivial "fix" for this.
Upvotes: 3