Scala中的[B>:A]做了什么?

时间:2011-10-13 19:23:21

标签: scala syntax

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)
}

3 个答案:

答案 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]表示Stackcovariant in A。如果XY的子类型,则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]DogInteger的唯一常见超类型。问题是编译器无法知道此调用是否安全,如果a: A被标记为协变,则不允许def push(a: A): Stack[A]中的Stack参数。如果它停在那里,协变栈将无用,因为没有办法将值放入其中。

签名解决了问题:

def push[B >: A](elem: B): Stack[B]

如果BA的祖先,则在添加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