Scala中[B >: A]
的含义是什么?有什么影响?
示例参考:http://www.scala-lang.org/node/129
class Stack[+A] {
def push[B >: A](elem: B): Stack[B] = new Stack[B] {
override def top: B = elem
override def pop: Stack[B] = Stack.this
override def toString() = elem.toString() + " " + Stack.this.toString()
}
def top: A = error("no element on stack")
def pop: Stack[A] = error("no element on stack")
override def toString() = ""
}
object VariancesTest extends Application {
var s: Stack[Any] = new Stack().push("hello");
s = s.push(new Object())
s = s.push(7)
println(s)
}
答案 0 :(得分:39)
[B >: A]
是一个较低的类型绑定。这意味着B
被限制为A
的超类型。
同样[B <: A]
是一个上限类型,意味着B
被约束为A
的子类型。
在您显示的示例中,您可以将类型为B
的元素推送到包含A
元素的堆栈,但结果是一堆B
个元素
您看到此页面的页面实际上有一个指向lower type bounds的其他页面的链接,其中包含一个显示效果的示例。
答案 1 :(得分:18)
X <: Y
表示类型参数X
必须是Y
类型的子类型。 X >: Y
表示相反,X
必须是Y
的超级类型(在这两种情况下,X = Y
都可以)。这种符号可能是相反的直觉,人们可能会认为狗不仅仅是一种动物(在编程术语中更精确,更多的服务),但由于其原因它更精确,狗的数量少于动物,类型Animal
包含的值多于Dog
类型,它包含所有狗,也包含所有鸵鸟。所以Animal
&gt;:Dog
。
至于push
有这个签名的原因,我不确定我能比这个例子的页面更好地解释它,但让我试试。
它以差异开始。 +
中的class Stack[+A]
表示Stack
为covariant in A
。如果X
是Y
的子类型,则Stack[X]
将是Stack[Y]
的子类型。一堆狗也是一堆动物。对于数学倾向,如果将Stack视为一个函数从一个类型到另一个类型(X是一个类型,如果你把它传递给Stack,你得到Stack [X],这是另一种类型),covariant意味着它是一个增加function(使用&lt;:,子类型关系是类型上的订单)。
这似乎是正确的,但这不是一个容易的问题。它不是这样,使用推送例程修改它,添加一个新元素,即
def push(a: A): Unit
(示例不同,push返回一个新堆栈,保持this
不变)。当然,Stack [Dog]应该只接受狗被推进去。否则,它将不再是一堆狗。但如果我们接受它被视为一堆动物,我们可以做到
val dogs : Stack[Dog] = new Stack[Dog]
val animals : Stack[Animal] = dogs // if we say stack is covariant
animals.push(ostrich) // allowed, we can push anything in a stack of any.
val topDog: Dog = dogs.top // ostrich!
显然,将此堆栈视为协变是不合理的。当堆栈被视为Stack[Animal]
时,允许不在Stack[Dog]
上的操作。这里用push执行的操作可以使用任何以A作为参数的例程来完成。如果泛型类被标记为协变,使用C [+ A],则A不能是C的任何(公共)例程的任何参数的类型,并且编译器将强制执行该操作。
但是例子中的堆栈是不同的。我们会有一个def push(a: A): Stack[A]
。如果一个人调用push
,一个人获得一个新的堆栈,并且原始堆栈保持不变,它仍然是一个正确的Stack [Dog],无论可能被推送。如果我们这样做
val newStack = dogs.push(ostrich)
dogs
仍然相同,仍为Stack[Dog]
。显然newStack
不是。也不是Stack[Ostrich]
,因为它还包含原始堆栈中(现在仍然是)的狗。但这将是一个合适的Stack[Animal]
。如果有人推猫,那就更准确地说它是Stack[Mammal]
(同时也是一堆动物)。如果推送12
,它将只是Stack[Any]
,Dog
和Integer
的唯一常见超类型。问题是编译器无法知道此调用是否安全,如果a: A
被标记为协变,则不允许def push(a: A): Stack[A]
中的Stack
参数。如果它停在那里,协变栈将无用,因为没有办法将值放入其中。
签名解决了问题:
def push[B >: A](elem: B): Stack[B]
如果B
是A
的祖先,则在添加B
时,会获得Stack[B]
。因此,向Mammal
添加Stack[Dog]
会产生Stack[Mammal]
,添加动物会产生Stack[Animal]
,这很好。添加狗也可以,A&gt;:A是真的。
这很好,但似乎限制太多了。如果添加的项目的类型不是A
的祖先怎么办?例如,如果它是后代,例如dogs.push(goldenRetriever)
,那该怎么办?一个人不能B = GoldenRetriever
,一个人没有GoldenRetriever >: Dog
,而是相反。然而,人们可以把B = Dog做好。参数elem预计是Dog类型,我们当然可以通过一个GoldenRetriever。一个人获得一堆B,仍然是一堆狗。并且B = GoldenRetriever
是不允许的。结果将被输入Stack[GoldenRetriever]
,这将是错误的,因为堆栈可能也包含爱尔兰的setter。
是什么东西?那么Ostrich
既不是超类型,也不是Dog
的子类型。但正如可以添加一个goldenRetriever,因为它是一只狗,并且可以添加一只狗,一只鸵鸟是一种动物,并且可以添加动物。因此,采取B =动物&gt;:狗工作,所以当推动鸵鸟时,一个人获得Stack[Animal]
。
使堆栈协变强制这个签名,比天真push(a: A) : Stack[A]
更复杂。但是我们获得了一个完全灵活的例程,可以添加任何东西,而不仅仅是A
,然而,尽可能精确地输入结果。除了类型声明之外,实际的实现与push(a: A)
的实现相同。
答案 2 :(得分:3)
作为一个很好的概述,请参阅@retronym的git page