Scala的协方差,逆变法如何有用?

时间:2014-05-14 10:50:36

标签: scala covariance contravariance

我一直在学习斯卡拉使用协方差和逆变参数化打字;我对以下示例代码感到有点困惑:

class Thing
class Creature extends Thing
class Human extends Creature
class Dog extends Creature

class Home[T >: Creature] {
  private var inside = Set[T]()

  def enter(entering: T) = inside += entering

  def whoIsInside = inside
}

val house = new Home[Thing]
val cage = new Home[Creature]
house enter new Human
cage enter new Dog

据我所知,参数化类Home使用了与Creature下限的逆差,所以

val flat = new Home[Human]

导致编译器错误,这是我的预期。我的困境是,我创造了一个新的“房子”,但我可以把“人”放进去!虽然这也是有道理的,因为'人'是'事',我天真地期待它失败!撇开机制,协方差,逆变是如何有用的?

2 个答案:

答案 0 :(得分:4)

在您的示例中,您可以将T的子类型放入T的集合中,因为U<:T也是T。这不是协方差或逆变。

协方差将是你的众议院[U]是众议院[T]的子类型为U<:T。所以如果你要求一个众议院[生物]你可以提供一个众议院[人类]如果T是协变的。我们在类型参数上使用+来表示协方差。

例如,

class Home[+T]
val cage: Home[Creature] = new Home[Human]

最有用的例子是当你使用Nil作为List时,因为Nil是List [Nothing]而List是协变的。所以List [Nothing]可以替代任何类型的List。

反方差则恰恰相反。如果U<:T,那么House [U]是House [T]的超类型。我们在类型参数上使用 - 来表示逆变。

例如,

class Home[-T]
val cage: Home[Human] = new Home[Creature]

答案 1 :(得分:3)

在回答问题之前,我需要注意两件事:

a)您在示例中没有使用协方差/逆变,您只需定义下限

b)在标题中,你暗示这是一个Scala概念。协方差和逆变是面向对象的一般概念,并且早在Scala之前就存在了。

关于你原来的问题,它有什么用?它允许您为参数化类型指定继承。例如,如果您将参数定义为covariant,也许将Creature作为上限,则可以表示狗的家可以代替生物的家:

scala> class Home[+T <: Creature]
defined class Home

scala> var home = new Home[Creature]
home: Home[Creature] = Home@46a32efb

scala> home = new Home[Dog]
home: Home[Creature] = Home@1b955e70

所以Home [Dog]是Home [Creature]的子类型 - covariance允许你表达这个。

另请注意,在您的示例中,只是使得类型参数协变不会编译,因为您允许输入。方法参数不能协变,因为这会破坏可替代性。 Scala编译器会为您检测到这一点。