为什么类型参数绑定的方法 >: 允许子类型?
Why does a method with type parameter bound >: allow subtypes?
考虑以下 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?
为什么允许这样做?
类型绑定 >:
是否意味着只应允许超类型?
def push[B >: A](x: B): Stack[B] = ...
...
Why is this allowed? Doesn't the type bound >:
mean that only supertypes should be allowed?
只允许 super-types 作为 B
,在您的示例 Apple
中。但是 x: B
处于逆变(输入)位置,因此您始终可以传递更具体的值作为参数。这与 B
的定义无关。但是,您将看到 honeycrisp
的推断类型是 Apple
而不是 Honeycrisp.
这确实令人费解,我记得曾经想过这个问题。但是,如果您仔细研究其含义,它确实保留了类型的健全性。当然,因此,从 push
的主体来看,x
确实是 Any
没有可以指望的特定功能。
可能相关:
考虑以下 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?
为什么允许这样做?
类型绑定 >:
是否意味着只应允许超类型?
def push[B >: A](x: B): Stack[B] = ...
...
Why is this allowed? Doesn't the type bound
>:
mean that only supertypes should be allowed?
只允许 super-types 作为 B
,在您的示例 Apple
中。但是 x: B
处于逆变(输入)位置,因此您始终可以传递更具体的值作为参数。这与 B
的定义无关。但是,您将看到 honeycrisp
的推断类型是 Apple
而不是 Honeycrisp.
这确实令人费解,我记得曾经想过这个问题。但是,如果您仔细研究其含义,它确实保留了类型的健全性。当然,因此,从 push
的主体来看,x
确实是 Any
没有可以指望的特定功能。
可能相关: