繁体   English   中英

Scala类型下限错误?

[英]Scala type lowerbound bug?

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的下限类型检查有bug ...不变大小写和反变例。

我不知道上面的代码是否有错误。

始终牢记,如果将Third扩展为Second那么每当需要Second ,都可以提供Third 这称为亚型多态性。

考虑到这一点, second.printInvariant(new Third)编译是很自然的。 您提供了Third ,它是Second的子类型,因此它签出。 这就像为苹果提供一种获取水果的方法一样。

这意味着你的方法

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

可以写成:

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

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

第二种错误情况也是如此-这是子类型多态的另一种情况。 您可以通过新的Third,因为Third实际上是Second(请注意,我在子类和超类之间使用的是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

现在,我们确实观察到了差异。 即使子类型多态允许我们在两种情况下都传递String,但只有方法foo1可以引用其参数的类型(在我们的情况下为String)。 方法foo2会很高兴地使用String,但是并不会真正知道它是String。 因此,当您想要保留类型时,上限可以派上用场(在您的情况下,您只需打印出值即可,因此您实际上并不关心类型-所有类型都有一个toString方法)。

编辑:
(更多细节,您可能已经知道了,但是为了完整起见,我将其说明)

上限比我在这里描述的更多,但是在参数化方法时,这是最常见的情况。 在对类进行参数化时,可以使用上限来描述协方差,而使用下限来描述逆方差。 例如,

class SomeClass[U] {

  def someMethod(foo: Foo[_ <: U]) = ???

}

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

这种差异声明被称为使用地点差异,因为我们在类型使用时声明类型的差异(在这里,它用作someMethod的方法参数类型)。 这是Java中唯一的一种方差声明。 使用使用地点差异时,您要小心“ 获取量”原理 (用Google搜索)。 基本上,这个原则说我们只能从协变类中获得东西(我们不能放进去),反之亦然(对于我们可以放进但不能得到)。 在我们的例子中,我们可以这样演示它:

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

更笼统地说,我们只能将协变类型用作返回类型,将反变量类型用作参数类型。

现在,使用站点声明很不错,但是在Scala中,利用声明站点差异(Java所没有的东西)更为常见。 这意味着我们将在定义Foo描述Foo的泛型类型的变化。 我们只说class Foo[+T] 现在,在编写与Foo一起使用的方法时,我们不需要使用界限; 我们宣布Foo在每种使用案例和每种情况下,其类型都是永久协变的。

有关Scala中差异的更多详细信息,请随时查看我关于此主题的博客文章

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM