【问题标题】:What does [B >: A] do in Scala?[B >: A] 在 Scala 中做了什么?
【发布时间】:2011-10-13 19:23:20
【问题描述】:

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

【问题讨论】:

  • 从上下文来看,它看起来像是“允许 B 表示一个也可由 A 表示的类”,但这只是猜测。
  • 在 REPL 上试试这个:val s1 = new Stack().push("hello"); val s2 = s1.push(new Object()); val s3 = s2.push(7) -- s1、s2 和 s3 有何不同? (这隐藏在给 s 的 Stack[Any] 类型后面;记住 Any 是 Scala 中最顶层的类型,而不是 Object。)
  • @macias 太糟糕了,当我在发布之前搜索类似问题时完全没有出现。我投票保留这些好答案..
  • @pyrony,标记为重复并不会删除答案,但构建重复网络实际上有助于查看问题的全局(以及所有答案)。我对你没有点击那篇文章并不感到惊讶,主题完全不同:-)。

标签: scala syntax


【解决方案1】:

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

同样[B <: A] 是一个上限类型,这意味着B 被限制为A 的子类型。

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

你看到这个的页面实际上有一个指向另一个关于lower type bounds的页面的链接,其中包含一个显示效果的示例。

【讨论】:

    【解决方案2】:

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

    至于push有这个签名的原因,我不确定我能不能比示例出处的页面更好地解释它,但让我试试。

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

    这似乎是对的,但这不是一个简单的问题。它不会是这样的,使用一个修改它的推送例程,添加一个新元素,即

    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,得到一个新堆栈,而原始堆栈保持不变,它仍然是一个适当的堆栈[狗],无论可能被压入。如果我们这样做

    val newStack = dogs.push(ostrich)
    

    dogs 仍然是相同的,仍然是 Stack[Dog]。显然newStack 不是。也不是Stack[Ostrich],因为它还包含曾经(现在仍然是)在原始堆栈中的狗。但这将是一个合适的Stack[Animal]。如果有人推了一只猫,更准确地说是Stack[Mammal](同时也是一堆动物)。如果推12,它将只是一个Stack[Any],这是DogInteger 的唯一共同超类型。问题是编译器无法知道这个调用是安全的,如果Stack 被标记为协变,编译器将不允许def push(a: A): Stack[A] 中的a: A 参数。如果它停在那里,协变堆栈将毫无用处,因为无法将值放入其中。

    签名解决问题:

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

    如果BA 的祖先,则添加B 时,将得到Stack[B]。因此,将Mammal 添加到Stack[Dog] 会得到Stack[Mammal],添加动物会得到Stack[Animal],这很好。添加 Dog 也可以,A >: A 是真的。

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

    鸵鸟呢? Ostrich 既不是超类型,也不是Dog 的子类型。但是就像一个人可以添加一只金毛猎犬,因为它是一只狗,并且可以添加一只狗,鸵鸟是一种动物,并且可以添加一只动物。所以取 B = Animal >: Dog 工作,所以当推鸵鸟时,一个人得到一个Stack[Animal]

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

    【讨论】:

    • 我不明白这个Stack is covariant in A。你能解释一下吗
    • 不确定我会比以前做得更好。说 Stack 在 A 中是协变的,就像说如果 X 是 Y 的子类型,则 Stack[X] 将是 Stack[Y] 的子类型,这也就是说 Stack[Dog] 是有效的 Stack[动物]。见docs.scala-lang.org/tour/variances.html
    【解决方案3】:

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

    【讨论】:

    • 真的值得一读。有一些例子可以连接上限/下限和视图边界。
    猜你喜欢
    • 2021-03-26
    • 1970-01-01
    • 2019-12-06
    • 2011-04-21
    • 2014-01-16
    • 1970-01-01
    • 1970-01-01
    • 2014-03-29
    • 1970-01-01
    相关资源
    最近更新 更多