3

考虑以下 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)
  ...
}

失败是因为Ain(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:如果一段代码期望 aStack[Fruit]将任何水果(香蕉或苹果)推到其上,那么它仍然可以将这些水果推到 a 上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?

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

4

1 回答 1

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

...

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

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

这确实令人困惑,我记得曾经想过这个问题。但是,如果您仔细研究其含义,它确实保留了类型的健全性。当然,因此,从身体的角度来看pushx它确实Any没有可以依赖的特定功能。


可能相关:

于 2015-12-30T15:55:02.007 回答