将一个泛型类型放在一个scala列表中

时间:2015-10-16 08:34:04

标签: scala generics

我正在尝试创建一个泛型类型并将它们放入列表中。这是我的代码。

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 ?

我期待x的类型是B,但编译器抱怨

_$1 where type _$1 <: com.example.cake.modules.robotics.B
    list.head.x(new B)

快速搜索我指向外卡的问题(_&lt;:B)。但不知道如何解决。

2 个答案:

答案 0 :(得分:3)

无形

的可能方法

由于以下部分中描述的原因,当您将元素放入列表时,您将丢失有关g1g2的确切类型的信息。 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

编译器拒绝代码

的原因说明

编译器所说的实际上是完全合情合理的(尽管有点模糊):请注意g2的类型为G[C]且其x成员仅接受C 1}}(或任何子类型)作为参数。

事实上:

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)
            ^

当你同时添加g1g2时,你会失去两者的精确类型,并且对于元素类型,最终需要安全的东西,无论你得到什么元素。 List[G[_ <: B]]表示对于列表中的每个元素,T中的G[T]需要T <: B,并且这是元素的唯一保证你拿出了清单。因此,最后,您使用list.head获得的是G[T <: B],因此T可以是B的任何子类型(这是一个无限集),因此,例如,如果是T = C,那么x只会接受C的子类型作为其t参数的值。这使得有效地无法为t提供值,因为T可能只是Nothing(没有价值的底层居住类型)。

事实上,如果你让scalac推断list的类型,你得到:

scala> val list = List(g1, g2)
list: List[G[_ >: C <: B]] = List(G@379619aa, G@cac736f)

表示T中的G[T]必须是B的子类型和C的超类型,这会将下限设置为T,从而允许通话list.head.x(new C)但不允许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)

重要的是要注意,如果不是使用head而是抓住列表list(1)的第二个元素,那么就会发生完全相同的事情。

为了弄清楚应该在这里做些什么,我们可能需要了解更多你想要实现的目标,因为没有那么简单的“修复”。

答案 1 :(得分:1)

使用方差的可能方法

选项1(在这种情况下更自然)逆变:

班级G在逆变位置使用类型T(作为x的参数)。 根据您的用例,您可以为类层次结构建模,以便将T的子类传递给x应该可以工作。这意味着您的示例代码可能类似于:

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)

选项2(带有一点肮脏的技巧)协方差:

如前所述,班级G在逆变位置使用类型T。 所以,如果你想(如你的示例代码中所示)是协变的,而不是被迫使用矛盾,那么你所要做的就是确保你只在&#34; covariant places&中使用类型T #34;,例如:

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)

请注意方法x上的奇怪类型约束:R >: T <: B只能写为R >: T,这意味着T的任何超类型,即你也可以得到:

list.head.x(new A)

进行编译,这就是为什么您需要R上的约束也是B的子类型