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

Jay*_*lor 34 syntax scala

[B >: A]在Scala 中意味着什么?有什么影响?

示例参考: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)
}
Run Code Online (Sandbox Code Playgroud)

Don*_*oby 41

[B >: A]是一种较低类型的界限.这意味着它B被限制为超类型A.

同样[B <: A]是一个上层类型绑定,意味着它B被约束为一个子类型A.

在您已经显示的示例中,您可以将类型元素推B送到包含A元素的堆栈中,但结果是一堆B元素.

您看到它的页面实际上有一个指向另一个关于较低类型边界的页面的链接,其中包含一个显示效果的示例.


Did*_*ont 18

X <: Y表示类型参数X必须是类型的子类型Y.X >: Y意思是相反,X必须是超级类型Y(在两种情况下X = Y都可以).这种符号可能是相反的直觉,人们可能会认为狗不仅仅是一种动物(在编程术语中更精确,更多的服务),但由于其原因它更精确,狗的数量少于动物,类型Animal包含的值多于类型Dog,它包含所有狗,也包含所有鸵鸟.所以Animal> : Dog.

至于为什么push有这个签名的原因,我不确定我能比这个例子的页面更好地解释它,但让我试试.

它以差异开始.将+class Stack[+A] 指Stackcovariant in A.if X是子类型Y,Stack[X] 将是子类型Stack[Y].一堆狗也是一堆动物.对于数学倾向,如果将Stack视为一个函数从一个类型到另一个类型(X是一个类型,如果你将它传递给Stack,你得到Stack [X],这是另一种类型),covariant意味着它是一个增加function(带<:,子类型关系是类型上的订单).

这似乎是正确的,但这不是一个容易的问题.它不会是这样,使用推送例程修改它,添加一个新元素,即

def push(a: A): Unit
Run Code Online (Sandbox Code Playgroud)

(示例不同,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!
Run Code Online (Sandbox Code Playgroud)

显然,将此堆栈视为协变是不合理的.当堆栈被视为a时Stack[Animal],允许一个不会打开的操作Stack[Dog].这里用push执行的操作可以使用任何以A作为参数的例程来完成.如果泛型类被标记为协变,使用C [+ A],则A不能是C的任何(公共)例程的任何参数的类型,并且编译器将强制执行该操作.

但是例子中的堆栈是不同的.我们会有一个def push(a: A): Stack[A].如果一个人调用push,就会得到一个新的堆栈,并且原始堆栈保持不变,它仍然是一个正确的Stack [Dog],无论什么可能被推送.如果我们这样做

val newStack = dogs.push(ostrich)
Run Code Online (Sandbox Code Playgroud)

dogs仍然是一样的,仍然是一个Stack[Dog].显然newStack不是.也不是一个Stack[Ostrich],因为它还包含原始堆栈中(并且仍然是)的狗.但这将是一个恰当的Stack[Animal].如果一个人推猫,那就更准确地说它是一个Stack[Mammal](同时也是一堆动物).如果一个推动12,它将只是一个Stack[Any],唯一的常见超类型DogInteger.问题是编译器无法知道此调用是否安全,并且不允许if中的a: A参数标记为covariant.如果它停在那里,协变栈将无用,因为没有办法将值放入其中.def push(a: A): Stack[A]Stack

签名解决了这个问题:

def push[B >: A](elem: B): Stack[B]
Run Code Online (Sandbox Code Playgroud)

如果BA添加a 的祖先B,则获得a Stack[B].因此,加入MammalStack[Dog]给出了一个Stack[Mammal],增加动物给出了一个Stack[Animal],这是罚款.添加狗也可以,A>:A是真的.

这很好,但似乎限制太多.如果添加的项目的类型不是其祖先A怎么办?例如,如果它是后代,例如dogs.push(goldenRetriever).一个B = GoldenRetriever人没有,一个没有GoldenRetriever >: Dog,但相反.然而,人们可以把B = Dog做好.参数elem预计是Dog类型,我们当然可以通过一个GoldenRetriever.一个人获得一堆B,仍然是一堆狗.这是不对的B = GoldenRetriever.结果将被输入为Stack[GoldenRetriever],这将是错误的,因为堆栈可能也包含爱尔兰的setter.

什么是东西?好吧Ostrich既不是超类型,也不是亚型Dog.但正如人们可以添加一个goldenRetriever,因为它是一只狗,并且可以添加一只狗,一只鸵鸟是一种动物,并且可以添加动物.因此,采取B =动物>:狗工作,所以当推动鸵鸟,一个得到一个Stack[Animal].

使堆栈协变迫使这个签名,比天真更复杂push(a: A) : Stack[A].但是我们获得了一个完全灵活的例程,任何东西都可以添加,而不仅仅是一个A,然而,尽可能精确地输入结果.除了类型声明之外,实际的实现与它本来一样push(a: A).