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

发布于 2024-12-09 11:24:30 字数 732 浏览 2 评论 0原文

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

What does [B >: A] mean in Scala? And what are the effects?

Example reference: 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)
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

傲性难收 2024-12-16 11:24:30

[B >: A] 是类型下限。这意味着 B 被限制为 A 的超类型。

类似地,[B <: A] 是类型上限,这意味着 B 被限制为 A 的子类型。

在您展示的示例中,您可以将 B 类型的元素推送到包含 A 元素的堆栈上,但结果是一个 堆栈B 元素。

您看到此内容的页面实际上有一个指向另一个关于较低类型界限的页面的链接,其中包括一个显示效果的示例。

[B >: A] is a lower type bound. It means that B is constrained to be a supertype of A.

Similarly [B <: A] is an upper type bound, meaning that B is constrained to be a subtype of 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.

The page where you saw this actually has a link to another page about lower type bounds, which includes an example showing the effect.

云巢 2024-12-16 11:24:30

X <: Y 表示类型参数 X 必须是类型 Y 的子类型。 X >: Y 表示相反,X 必须是 Y 的超类型(在这两种情况下,X = Y< /代码> 就可以了)。这种表示法可能与直觉相反,人们可能认为狗不仅仅是一种动物(在编程术语中更精确,更多的服务),但正是因为它更精确,狗的数量比动物的数量少,类型AnimalDog 类型包含更多的值,它包含所有狗,也包含所有鸵鸟。所以动物>:

至于为什么 push 有这个签名,我不确定我能比示例来自的页面更好地解释它,但让我尝试一下。

它从方差开始。 class Stack[+A] 中的 + 表示 Stack 在 A 中是协变的。如果 XY 的子类型,则 Stack[X] 将是 Stack[Y] 的子类型>。一堆狗也是一堆动物。对于数学倾向的人来说,如果人们将 Stack 视为从类型到类型的函数(X 是一种类型,如果将其传递给 Stack,则会得到 Stack[X],这是另一种类型),协变意味着它是一个递增的函数(带有 <:,子类型关系是类型的顺序)。

这似乎是正确的,但这并不是一个简单的问题。情况并非如此,如果使用push例程修改它,添加一个新元素,也就是说

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 的唯一公共超类型>。问题是编译器无法知道此调用是否安全,并且不允许在 def push(a: A): Stack[A]< 中使用 a: A 参数/code> 如果 Stack 被标记为协变。如果它停在那里,协变堆栈将毫无用处,因为无法将值放入其中。

签名解决了这个问题:

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

如果BA的祖先,当添加一个B时,会得到一个Stack[B]。因此,向 Stack[Dog] 添加一个 Mammal 会得到一个 Stack[Mammal],添加一个动物会得到一个 Stack[Animal],这很好。添加狗也可以,A >:A 为真。

这很好,但似乎限制太多。如果添加的项的类型不是 A 的祖先怎么办?例如,如果它是后代,例如dogs.push(goldenRetriever),该怎么办?不能拿B = GoldenRetriever,也不能拿GoldenRetriever >: Dog,反之亦然。然而,我们可以认为 B = 狗。如果参数elem预计是Dog类型,我们当然可以传递一个GoldenRetriever。一个得到一堆B,仍然是一堆狗。确实不允许 B = GoldenRetriever。结果将被输入为 Stack[GoldenRetriever],这是错误的,因为堆栈也可能包含爱尔兰 setter。

鸵鸟呢?那么,Ostrich 既不是 Dog 的超类型,也不是 Dog 的子类型。但正如我们可以添加一只金毛猎犬,因为它是一只狗,并且可以添加一只狗一样,鸵鸟是一种动物,并且可以添加一只动物。因此,取 B = Animal >: Dog 有效,因此当推鸵鸟时,会得到一个 Stack[Animal]。

使堆栈协变强制此签名,比简单的 push(a: A) : Stack[A] 更复杂。但是我们获得了一个完全灵活的例程,可以添加任何内容,而不仅仅是 A,并且可以尽可能精确地输入结果。除了类型声明之外,实际的实现与 push(a: A) 相同。

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.

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.

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

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

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

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.

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

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.

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.

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.

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].

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

甜`诱少女 2024-12-16 11:24:30

作为一个很好的概述,请参阅 @retronym 的 git 页面

As a great overview, see the git page of @retronym

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文