【问题标题】:Scala type lowerbound bug?Scala 类型的下限错误?
【发布时间】:2016-04-13 06:54:30
【问题描述】:
case class Level[B](b: B){
  def printCovariant[A<:B](a: A): Unit = println(a)
  def printInvariant(b: B): Unit = println(b)
  def printContravariant[C>:B](c: C): Unit = println(c)
}

class First
class Second extends First
class Third extends Second

//First >: Second >: Third

object Test extends App {

  val second = Level(new Second) //set B as Second

  //second.printCovariant(new First) //error and reasonable
  second.printCovariant(new Second) 
  second.printCovariant(new Third) 

  //second.printInvariant(new First) //error and reasonable
  second.printInvariant(new Second) 
  second.printInvariant(new Third) //why no error?

  second.printContravariant(new First) 
  second.printContravariant(new Second)
  second.printContravariant(new Third) //why no error?
}

似乎 scala 的下限类型检查有错误......对于不变的情况和逆变的情况。

我想知道上面的代码有没有错误。

【问题讨论】:

    标签: scala types lower-bound


    【解决方案1】:

    请始终牢记,如果 Third 扩展了 Second,那么只要需要 Second,就可以提供 Third。这称为亚型多态性。

    考虑到这一点,second.printInvariant(new Third) 编译是很自然的。您提供了一个Third,它是Second 的子类型,因此它会签出。这就像将一个 Apple 提供给一个采用 Fruit 的方法。

    这意味着你的方法

    def printCovariant[A<:B](a: A): Unit = println(a)
    

    可以写成:

    def printCovariant(a: B): Unit = println(a)
    

    不会丢失任何信息。由于子类型多态性,第二个接受 B 及其所有子类,与第一个相同。

    您的第二个错误情况也是如此 - 这是子类型多态的另一种情况。您可以传递新的第三个,因为第三个实际上是第二个(请注意,我在子类和超类之间使用“is-a”关系,取自面向对象的表示法)。

    如果您想知道为什么我们甚至需要上限(子类型多态性还不够吗?),请观察以下示例:

    def foo1[A <: AnyRef](xs: A) = xs
    def foo2(xs: AnyRef) = xs
    val res1 = foo1("something") // res1 is a String
    val res2 = foo2("something") // res2 is an Anyref
    

    现在我们确实观察到了差异。尽管子类型多态性允许我们在两种情况下都传入一个字符串,但只有方法foo1 可以引用其参数的类型(在我们的例子中是一个字符串)。方法foo2 很乐意接受一个字符串,但不会真正知道它是一个字符串。因此,当您想要保留类型时,上限可以派上用场(在您的情况下,您只需打印出值,因此您并不真正关心类型 - 所有类型都有一个 toString 方法)。

    编辑:
    (额外的细节,你可能已经知道了,但我会为了完整起见)

    上界的用途比我在这里描述的要多,但是在参数化方法时,这是最常见的情况。当参数化一个类时,你可以使用上限来描述协方差和下限来描述逆变。例如,

    class SomeClass[U] {
    
      def someMethod(foo: Foo[_ <: U]) = ???
    
    }
    

    表示方法someMethod 的参数foo 在其类型上是协变的。怎么样?好吧,通常情况下(也就是说,没有调整方差),子类型多态性不允许我们传递使用其类型参数的子类型参数化的Foo。如果T &lt;: U,那并不意味着Foo[T] &lt;: Foo[U]。我们说Foo 的类型是不变的。但我们只是调整了方法以接受使用U其任何子类型 参数化的Foo。现在这实际上是协方差。所以,只要关注someMethod——如果某个类型TU的子类型,那么Foo[T]Foo[U]的子类型。太好了,我们实现了协方差。但请注意,我说“只要关注someMethod”。 Foo 在此方法中的类型是协变的,但在其他方法中它可能是不变的或逆变的。

    这种方差声明称为use-site方差,因为我们在使用时声明了一个类型的方差(这里它被用作someMethod的方法参数类型)。这是 Java 中唯一的一种差异声明。使用使用站点差异时,您必须注意 get-put 原则(google it)。基本上,这个原则说我们只能从协变类中获取东西(我们不能放),反之亦然,对于逆变类(我们可以放但不能得到)。在我们的例子中,我们可以这样演示:

    class Foo[T] { def put(t: T): Unit = println("I put some T") }
    
    def someMethod(foo: Foo[_ <: String]) = foo.put("asd") // won't compile
    def someMethod2(foo: Foo[_ >: String]) = foo.put("asd")
    

    更一般地说,我们只能将协变类型用作返回类型,将逆变类型用作参数类型。

    现在,use-site 声明很好,但在 Scala 中,利用 declaration-site 差异(Java 没有)更为常见。这意味着我们将在定义Foo 时描述Foo 的泛型类型的差异。我们会简单地说class Foo[+T]。现在我们在编写与Foo 一起使用的方法时不需要使用边界;我们宣布Foo 在其类型、每个用例和每个场景中都是永久协变的。

    有关 Scala 差异的更多详细信息,请随时查看我的 blog post 关于此主题的内容。

    【讨论】:

      猜你喜欢
      • 2015-10-15
      • 2018-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-07-01
      • 1970-01-01
      相关资源
      最近更新 更多