为什么类型参数绑定>的方法允许子类型?

时间:2015-12-30 15:34:25

标签: scala type-bounds

在Scala中考虑以下简单的堆栈实现:

abstract class Stack[+A] {
  def top: A
  def pop: Stack[A]
}

case object EmptyStack extends Stack[Nothing] {
  def top = error("EmptyStack.top")
  def pop = error("EmptyStack.pop")
}

case class NonEmptyStack[A](elem: A, rest: Stack[A]) extends Stack[A] {
  def top = elem
  def pop = rest
}

现在假设我们要向push添加Stack方法。 天真的尝试

abstract class Stack[+A] {
  def push(x: A): Stack[A] = new NonEmptyStack[A](x, this)
  ...
}

失败,因为A中的(x: A)是逆变位置。 在Scala by Example第58页,作者建议

def push[B >: A](x: B): Stack[B] = new NonEmptyStack[B](x, this)

这里绑定的类型意味着给定某个类型的堆栈,我们可以将相等或更通用类型的对象推送到该堆栈,结果是更通用类型的堆栈

例如,

class Fruit
class Apple extends Fruit
class Banana extends Fruit

val apple = new Apple
val banana = new Banana

val stack1 = EmptyStack.push(apple)  // Stack[Apple]
val stack2 = stack1.push(banana)     // Stack[Fruit]

我认为这个选择的重点在于它确实保持了Stack的协方差:如果一段代码需要Stack[Fruit]它将推送任何水果(香蕉或苹果),那么它仍然可以将这些水果推到Stack[Apple]

令人惊讶的是,我们也可以推送子类型:

class Honeycrisp extends Apple

val honeycrisp = Honeycrisp
val stack1 = EmptyStack.push(apple)  // Stack[Apple]
val stack2 = stack1.push(honeycrisp) // Stack[Apple], why does this work?

为什么允许这样做? 类型绑定>:是否意味着只允许超类型?

1 个答案:

答案 0 :(得分:3)

def push[B >: A](x: B): Stack[B] = ...
     

...

     

为什么允许这样做?类型绑定>:是否意味着只允许超类型?

在您的示例B中,只允许超级类型为Apple。但是x: B处于逆变(输入)位置,因此您总是可以传递更具体的值作为参数。这与B的定义无关。但是,您会看到honeycrisp的推断类型为Apple而不是Honeycrisp.

这确实令人困惑,我记得曾经曾经想过这件事。但是,如果你仔细研究它,它确实保留了类型的健全性。当然,从push的主体的角度来看,x实际上是Any,没有特定的能力,它可以指望。

可能相关: