【问题标题】:Why does a method with type parameter bound >: allow subtypes?为什么类型参数绑定的方法>:允许子类型?
【发布时间】:2015-12-31 00:23:23
【问题描述】:

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

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

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

失败是因为(x: A) 中的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?

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

【问题讨论】:

    标签: scala type-bounds


    【解决方案1】:
    def push[B >: A](x: B): Stack[B] = ...
    

    ...

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

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

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


    可能相关:

    【讨论】:

    • "但是如果你去考虑后果,它确实保留了类型的健全性。"当然是。我遇到这个是因为担心 >: 会阻止传递子类型的能力,我试图弄清楚如何恢复该能力。我很高兴它能按它的方式工作。正如你所说,我只是感到困惑。您的回答强调类型参数和输入位置的协方差之间的差异,这一切都清楚了。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-06
    • 2021-08-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-27
    相关资源
    最近更新 更多