What does [B >: A]
mean in Scala? And what are the effects?
[B >: A]在Scala中是什么意思?效果如何?
Example reference: http://www.scala-lang.org/node/129
参考示例: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 个解决方案
#1
31
[B >: A]
is a lower type bound. It means that B
is constrained to be a supertype of A
.
[B >: A]是下界的界。这意味着B被约束为a的超类。
Similarly [B <: A]
is an upper type bound, meaning that B
is constrained to be a subtype of A
.
类似地,[B <: A]是一个上类型约束,这意味着B被约束为A的子类型。
In the example you've shown, you're allowed to push an element of type B
onto a stack containing A
elements, but the result is a stack of B
elements.
在您展示的示例中,允许将B类型的元素推到包含元素的堆栈中,但结果是B元素的堆栈。
The page where you saw this actually has a link to another page about lower type bounds, which includes an example showing the effect.
您所看到的页面实际上有一个链接到另一个关于下类型界限的页面,其中包括一个显示效果的示例。
#2
14
X <: Y
means type parameter X
must be a subtype of type Y
. X >: Y
means the opposite, X
must be a super type of Y
(in both cases, X = Y
is ok). This notation can be contrary intuition, one may think a dog is more than an animal (more precise of in programming terms, more services), but for the very reason it is more precise, there are less dogs than there are animals, the type Animal
contains more values than the type Dog
, it contains all dogs, and all ostriches too. So Animal
>: Dog
.
X <: Y表示类型参数X必须是Y类型的子类型。Y的意思是相反的,X必须是一个超类型的Y(在这两种情况下,X = Y是可以的)。这个符号可以直觉相反,人们可能会认为狗比一个动物(更精确的在编程方面,更多的服务),但是非常的原因更精确,有狗不如动物,动物类型包含的值超过了类型的狗,它包含所有的狗,和所有的鸵鸟。所以动物>:狗。
As for the reason why push
has this signature, I'm not sure I can explain it better than the page the example comes from, but let me try.
至于为什么push有这个签名,我不确定是否能比这个例子的页面更好地解释它,但是让我试试。
It starts with variance. The +
in class Stack[+A]
means that Stack
is covariant in A
. if X
is a subtype of Y
, Stack[X]
will be a subtype of Stack[Y]
. A stack of dogs is also a stack of animals. For the mathematically inclined, if one sees Stack as a function from type to type (X is a type, if you pass it to Stack, you get Stack[X], which is another type), being covariant means that it is an increasing function (with <:, the subtyping relation being the orders on types).
它始于方差。类堆栈中的+表示堆栈在A中是协变的,如果X是Y的子类型,Stack[X]将是堆栈的子类型[Y]。一堆狗也是一堆动物。擅长数学,如果一个人认为堆栈是一种函数类型(X是一个类型,如果你将它传递给堆栈,堆栈[X],这是另一种类型),协变意味着它是一个递增函数(<:,子类型关系的订单类型)。
This seems right, but this is not such an easy question. It would not be so, with a push routine that modifies it, adding a new element, that is
这似乎是正确的,但这并不是一个简单的问题。它不会是这样,用一个push例程来修改它,添加一个新元素。
def push(a: A): Unit
(the example is different, push returns a new stack, leaving this
unchanged). Of course, a Stack[Dog] should only accept dogs to be pushed into it. Otherwise, it would no longer be a stack of dogs. But if we accept it to be treated as a stack of animals, we could do
(例子是不同的,push返回一个新堆栈,保持不变)。当然,狗狗只应该接受狗狗的推入。否则,它将不再是一堆狗。但如果我们接受它被当作一堆动物对待,我们就能做到。
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!
Clearly, treating this stack as covariant is unsound. When the stack is seen as a Stack[Animal]
, an operation is allowed that would not be on Stack[Dog]
. What was done here with push can be done with any routine that takes A as its argument. If a generic class is marked as covariant, with C[+A], then A cannot be the type of any argument of any (public) routine of C, and the compiler will enforce that.
显然,将此堆栈视为协变是不正确的。当堆栈被看作是一个堆栈[动物]时,一个操作是允许的,它不会在堆栈[狗]上。在这里所做的事情,可以用任何例行的方法来完成。如果泛型类被标记为协变,使用C[+ a],那么a就不能成为C的任何(公共)例程的任何参数的类型,而编译器将强制执行它。
But the stack in the exemple is different. We would have a def push(a: A): Stack[A]
. If one calls push
, one gets a new stack, and the original stack is left unchanged, it is still a proper Stack[Dog], whatever may have been pushed. If we do
但是例中的堆栈是不同的。我们会有一个def push(a: a): Stack[a]。如果调用push,就会得到一个新的堆栈,而原来的堆栈保持不变,它仍然是一个合适的堆栈[Dog],不管被push的是什么。如果我们做
val newStack = dogs.push(ostrich)
dogs
is still the same and still a Stack[Dog]
. Obviously newStack
is not. Nor is it a Stack[Ostrich]
, because it also contains the dogs that were (and still are) in the original stack. But it would be a proper Stack[Animal]
. If one pushes a cat, it would be more precise to say it is a Stack[Mammal]
(while being a stack of animals too). If one pushes 12
, it will be only a Stack[Any]
, the only common supertype of Dog
and Integer
. The problem is that the compiler has no way to know that this call is safe, and will not allow the a: A
argument in def push(a: A): Stack[A]
if Stack
is marked covariant. If it stopped there, a covariant stack would be useless because there would be no way to put values in it.
狗仍然是一样的,仍然是一个堆栈[狗]。显然newStack不是。它也不是一堆(鸵鸟),因为它也包含了那些在原来的堆栈中(现在仍然是)的狗。但这将是一个合适的堆栈[动物]。如果一个人推一只猫,就更确切地说它是一堆(哺乳动物)(同时也是一堆动物)。如果一个人推12,它将只是一个堆栈[任何],唯一普通的超类型的狗和整数。问题是编译器没有办法知道这个调用是安全的,并且不会允许a:在def push(a: a)中的一个参数:如果堆栈是有标记的协变的,那么堆栈[a]。如果它在那里停止,一个协变堆栈将是无用的,因为没有办法将值放入其中。
The signature solves the problem:
签名解决了问题:
def push[B >: A](elem: B): Stack[B]
If B
is an ancestor of A
, when adding a B
, one gets a Stack[B]
. So adding a Mammal
to a Stack[Dog]
gives a Stack[Mammal]
, adding an animal gives a Stack[Animal]
, which is fine. Adding a Dog is ok too, A >: A is true.
如果B是A的祖先,在添加B时,会得到一个堆栈[B]。所以,把哺乳动物加到一堆(狗狗)中,就会得到一堆[哺乳动物],再加上一种动物,就会得到一堆[动物],这很好。添加一条狗也可以,一个>:a是真的。
This is good, but seems too restrictive. What if the added item's type is not an ancestor of A
? For instance, what if it is a descendant e.g dogs.push(goldenRetriever)
. One cannnot take B = GoldenRetriever
, one has not GoldenRetriever >: Dog
, but the opposite. Yet, one can take B = Dog all right. It the parameter elem is expected to be of type Dog, we can pass of course pass a GoldenRetriever. One gets a stack of B, still a stack of dogs. And it is right that B = GoldenRetriever
was not allowed. The result would have been typed as Stack[GoldenRetriever]
, which would be wrong because the stack may have contained irish setters too.
这很好,但似乎太严格了。如果添加的项的类型不是A的祖先,该怎么办?例如,如果它是一个后代e。g dogs.push(goldenRetriever)。一个人不带B = GoldenRetriever,一个人没有GoldenRetriever >: Dog,但是相反。不过,你可以把B =狗狗带走。它的参数elem被认为是类型狗,我们当然可以通过一只GoldenRetriever。一个人得到一堆B,仍然是一堆狗。而且,B = GoldenRetriever是不允许的。结果将被输入为堆栈[GoldenRetriever],这是错误的,因为堆栈可能也包含了爱尔兰setter。
What about ostrishes? Well Ostrich
is neither an supertype, nor a subtype of Dog
. But just as one can add a goldenRetriever because it is a dog, and it is possible to add a dog, an ostrich is an animal, and it is possible to add an animal. So taking B = Animal >: Dog works, and so a when pushing an ostrich, one gets a Stack[Animal]
.
ostrishes呢?鸵鸟既不是超型,也不是狗的亚型。但是,就像一个人可以添加一只金毛猎犬,因为它是一只狗,而且可以添加一条狗,鸵鸟是一种动物,并且可以添加动物。所以取B =动物>:狗的工作,所以当推一只鸵鸟时,一个人得到一个堆栈[动物]。
Making the stack covariant force this signature, more complex than the naïve push(a: A) : Stack[A]
. But we gain a routine that is completely flexible, anything can be added, not just an A
, and yet, types the result as precisely as can be. And the actual implementation, except for the types declarations, is the same it would have been with push(a: A)
.
使堆栈协变强制这个签名,比单纯的push(a: a):堆栈[a]更复杂。但是我们获得了一个完全灵活的例程,任何东西都可以被添加,而不仅仅是a,而且还可以精确地输入结果。除了类型声明之外,实际实现与push(a: a)相同。
#1
31
[B >: A]
is a lower type bound. It means that B
is constrained to be a supertype of A
.
[B >: A]是下界的界。这意味着B被约束为a的超类。
Similarly [B <: A]
is an upper type bound, meaning that B
is constrained to be a subtype of A
.
类似地,[B <: A]是一个上类型约束,这意味着B被约束为A的子类型。
In the example you've shown, you're allowed to push an element of type B
onto a stack containing A
elements, but the result is a stack of B
elements.
在您展示的示例中,允许将B类型的元素推到包含元素的堆栈中,但结果是B元素的堆栈。
The page where you saw this actually has a link to another page about lower type bounds, which includes an example showing the effect.
您所看到的页面实际上有一个链接到另一个关于下类型界限的页面,其中包括一个显示效果的示例。
#2
14
X <: Y
means type parameter X
must be a subtype of type Y
. X >: Y
means the opposite, X
must be a super type of Y
(in both cases, X = Y
is ok). This notation can be contrary intuition, one may think a dog is more than an animal (more precise of in programming terms, more services), but for the very reason it is more precise, there are less dogs than there are animals, the type Animal
contains more values than the type Dog
, it contains all dogs, and all ostriches too. So Animal
>: Dog
.
X <: Y表示类型参数X必须是Y类型的子类型。Y的意思是相反的,X必须是一个超类型的Y(在这两种情况下,X = Y是可以的)。这个符号可以直觉相反,人们可能会认为狗比一个动物(更精确的在编程方面,更多的服务),但是非常的原因更精确,有狗不如动物,动物类型包含的值超过了类型的狗,它包含所有的狗,和所有的鸵鸟。所以动物>:狗。
As for the reason why push
has this signature, I'm not sure I can explain it better than the page the example comes from, but let me try.
至于为什么push有这个签名,我不确定是否能比这个例子的页面更好地解释它,但是让我试试。
It starts with variance. The +
in class Stack[+A]
means that Stack
is covariant in A
. if X
is a subtype of Y
, Stack[X]
will be a subtype of Stack[Y]
. A stack of dogs is also a stack of animals. For the mathematically inclined, if one sees Stack as a function from type to type (X is a type, if you pass it to Stack, you get Stack[X], which is another type), being covariant means that it is an increasing function (with <:, the subtyping relation being the orders on types).
它始于方差。类堆栈中的+表示堆栈在A中是协变的,如果X是Y的子类型,Stack[X]将是堆栈的子类型[Y]。一堆狗也是一堆动物。擅长数学,如果一个人认为堆栈是一种函数类型(X是一个类型,如果你将它传递给堆栈,堆栈[X],这是另一种类型),协变意味着它是一个递增函数(<:,子类型关系的订单类型)。
This seems right, but this is not such an easy question. It would not be so, with a push routine that modifies it, adding a new element, that is
这似乎是正确的,但这并不是一个简单的问题。它不会是这样,用一个push例程来修改它,添加一个新元素。
def push(a: A): Unit
(the example is different, push returns a new stack, leaving this
unchanged). Of course, a Stack[Dog] should only accept dogs to be pushed into it. Otherwise, it would no longer be a stack of dogs. But if we accept it to be treated as a stack of animals, we could do
(例子是不同的,push返回一个新堆栈,保持不变)。当然,狗狗只应该接受狗狗的推入。否则,它将不再是一堆狗。但如果我们接受它被当作一堆动物对待,我们就能做到。
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!
Clearly, treating this stack as covariant is unsound. When the stack is seen as a Stack[Animal]
, an operation is allowed that would not be on Stack[Dog]
. What was done here with push can be done with any routine that takes A as its argument. If a generic class is marked as covariant, with C[+A], then A cannot be the type of any argument of any (public) routine of C, and the compiler will enforce that.
显然,将此堆栈视为协变是不正确的。当堆栈被看作是一个堆栈[动物]时,一个操作是允许的,它不会在堆栈[狗]上。在这里所做的事情,可以用任何例行的方法来完成。如果泛型类被标记为协变,使用C[+ a],那么a就不能成为C的任何(公共)例程的任何参数的类型,而编译器将强制执行它。
But the stack in the exemple is different. We would have a def push(a: A): Stack[A]
. If one calls push
, one gets a new stack, and the original stack is left unchanged, it is still a proper Stack[Dog], whatever may have been pushed. If we do
但是例中的堆栈是不同的。我们会有一个def push(a: a): Stack[a]。如果调用push,就会得到一个新的堆栈,而原来的堆栈保持不变,它仍然是一个合适的堆栈[Dog],不管被push的是什么。如果我们做
val newStack = dogs.push(ostrich)
dogs
is still the same and still a Stack[Dog]
. Obviously newStack
is not. Nor is it a Stack[Ostrich]
, because it also contains the dogs that were (and still are) in the original stack. But it would be a proper Stack[Animal]
. If one pushes a cat, it would be more precise to say it is a Stack[Mammal]
(while being a stack of animals too). If one pushes 12
, it will be only a Stack[Any]
, the only common supertype of Dog
and Integer
. The problem is that the compiler has no way to know that this call is safe, and will not allow the a: A
argument in def push(a: A): Stack[A]
if Stack
is marked covariant. If it stopped there, a covariant stack would be useless because there would be no way to put values in it.
狗仍然是一样的,仍然是一个堆栈[狗]。显然newStack不是。它也不是一堆(鸵鸟),因为它也包含了那些在原来的堆栈中(现在仍然是)的狗。但这将是一个合适的堆栈[动物]。如果一个人推一只猫,就更确切地说它是一堆(哺乳动物)(同时也是一堆动物)。如果一个人推12,它将只是一个堆栈[任何],唯一普通的超类型的狗和整数。问题是编译器没有办法知道这个调用是安全的,并且不会允许a:在def push(a: a)中的一个参数:如果堆栈是有标记的协变的,那么堆栈[a]。如果它在那里停止,一个协变堆栈将是无用的,因为没有办法将值放入其中。
The signature solves the problem:
签名解决了问题:
def push[B >: A](elem: B): Stack[B]
If B
is an ancestor of A
, when adding a B
, one gets a Stack[B]
. So adding a Mammal
to a Stack[Dog]
gives a Stack[Mammal]
, adding an animal gives a Stack[Animal]
, which is fine. Adding a Dog is ok too, A >: A is true.
如果B是A的祖先,在添加B时,会得到一个堆栈[B]。所以,把哺乳动物加到一堆(狗狗)中,就会得到一堆[哺乳动物],再加上一种动物,就会得到一堆[动物],这很好。添加一条狗也可以,一个>:a是真的。
This is good, but seems too restrictive. What if the added item's type is not an ancestor of A
? For instance, what if it is a descendant e.g dogs.push(goldenRetriever)
. One cannnot take B = GoldenRetriever
, one has not GoldenRetriever >: Dog
, but the opposite. Yet, one can take B = Dog all right. It the parameter elem is expected to be of type Dog, we can pass of course pass a GoldenRetriever. One gets a stack of B, still a stack of dogs. And it is right that B = GoldenRetriever
was not allowed. The result would have been typed as Stack[GoldenRetriever]
, which would be wrong because the stack may have contained irish setters too.
这很好,但似乎太严格了。如果添加的项的类型不是A的祖先,该怎么办?例如,如果它是一个后代e。g dogs.push(goldenRetriever)。一个人不带B = GoldenRetriever,一个人没有GoldenRetriever >: Dog,但是相反。不过,你可以把B =狗狗带走。它的参数elem被认为是类型狗,我们当然可以通过一只GoldenRetriever。一个人得到一堆B,仍然是一堆狗。而且,B = GoldenRetriever是不允许的。结果将被输入为堆栈[GoldenRetriever],这是错误的,因为堆栈可能也包含了爱尔兰setter。
What about ostrishes? Well Ostrich
is neither an supertype, nor a subtype of Dog
. But just as one can add a goldenRetriever because it is a dog, and it is possible to add a dog, an ostrich is an animal, and it is possible to add an animal. So taking B = Animal >: Dog works, and so a when pushing an ostrich, one gets a Stack[Animal]
.
ostrishes呢?鸵鸟既不是超型,也不是狗的亚型。但是,就像一个人可以添加一只金毛猎犬,因为它是一只狗,而且可以添加一条狗,鸵鸟是一种动物,并且可以添加动物。所以取B =动物>:狗的工作,所以当推一只鸵鸟时,一个人得到一个堆栈[动物]。
Making the stack covariant force this signature, more complex than the naïve push(a: A) : Stack[A]
. But we gain a routine that is completely flexible, anything can be added, not just an A
, and yet, types the result as precisely as can be. And the actual implementation, except for the types declarations, is the same it would have been with push(a: A)
.
使堆栈协变强制这个签名,比单纯的push(a: a):堆栈[a]更复杂。但是我们获得了一个完全灵活的例程,任何东西都可以被添加,而不仅仅是a,而且还可以精确地输入结果。除了类型声明之外,实际实现与push(a: a)相同。