简体   繁体   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?
}

It seems scala's lowerbound type checking has bugs... for invariant case and contravariant case. 似乎scala的下限类型检查有bug ...不变大小写和反变例。

I wonder above code are have bugs or not. 我不知道上面的代码是否有错误。

Always keep in mind that if Third extends Second then whenever a Second is wanted, a Third can be provided. 始终牢记,如果将Third扩展为Second那么每当需要Second ,都可以提供Third This is called subtype polymorhpism. 这称为亚型多态性。

Having that in mind, it's natural that second.printInvariant(new Third) compiles. 考虑到这一点, second.printInvariant(new Third)编译是很自然的。 You provided a Third which is a subtype of Second , so it checks out. 您提供了Third ,它是Second的子类型,因此它签出。 It's like providing an Apple to a method which takes a Fruit. 这就像为苹果提供一种获取水果的方法一样。

This means that your method 这意味着你的方法

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

can be written as: 可以写成:

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

without losing any information. 不会丢失任何信息。 Due to subtype polymorphism, the second one accepts B and all its subclasses, which is the same as the first one. 由于子类型多态性,第二个接受B及其所有子类,这与第一个相同。

Same goes for your second error case - it's another case of subtype polymorphism. 第二种错误情况也是如此-这是子类型多态的另一种情况。 You can pass the new Third because Third is actually a Second (note that I'm using the " is-a " relationship between subclass and superclass taken from object-oriented notation). 您可以通过新的Third,因为Third实际上是Second(请注意,我在子类和超类之间使用的是is-a关系,它取自面向对象的表示法)。

In case you're wondering why do we even need upper bounds (isn't subtype polymorphism enough?), observe this example: 如果您想知道为什么我们甚至还需要上限(子类型多态性不够吗?),请观察以下示例:

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

Now we do observe the difference. 现在,我们确实观察到了差异。 Even though subtype polymorphism will allow us to pass in a String in both cases, only method foo1 can reference the type of its argument (in our case a String). 即使子类型多态允许我们在两种情况下都传递String,但只有方法foo1可以引用其参数的类型(在我们的情况下为String)。 Method foo2 will happily take a String, but will not really know that it's a String. 方法foo2会很高兴地使用String,但是并不会真正知道它是String。 So, upper bounds can come in handy when you want to preserve the type (in your case you just print out the value so you don't really care about the type - all types have a toString method). 因此,当您想要保留类型时,上限可以派上用场(在您的情况下,您只需打印出值即可,因此您实际上并不关心类型-所有类型都有一个toString方法)。

EDIT: 编辑:
(extra details, you may already know this but I'll put it for completeness) (更多细节,您可能已经知道了,但是为了完整起见,我将其说明)

There are more uses of upper bounds then what I described here, but when parameterizing a method this is the most common scenario. 上限比我在这里描述的更多,但是在参数化方法时,这是最常见的情况。 When parameterizing a class, then you can use upper bounds to describe covariance and lower bounds to describe contravariance. 在对类进行参数化时,可以使用上限来描述协方差,而使用下限来描述逆方差。 For example, 例如,

class SomeClass[U] {

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

}

says that parameter foo of method someMethod is covariant in its type. 表示方法someMethod参数foo在类型上是协变的。 How's that? 怎么样? Well, normally (that is, without tweaking variance), subtype polymorphism wouldn't allow us to pass a Foo parameterized with a subtype of its type parameter. 好吧,通常情况下(即没有调整方差),子类型多态性不允许我们传递带有其类型参数的子类型的Foo参数。 If T <: U , that doesn't mean that Foo[T] <: Foo[U] . 如果T <: U ,并不意味着Foo[T] <: Foo[U] We say that Foo is invariant in its type. 我们说Foo在类型上是不变的。 But we just tweaked the method to accept Foo parameterized with U or any of its subtypes . 但是我们只是对方法进行了调整,以接受使用U或其任何子类型参数化的Foo Now that is effectively covariance. 现在,这实际上是协方差。 So, as long as someMethod is concerned - if some type T is a subtype of U , then Foo[T] is a subtype of Foo[U] . 因此,只要关注someMethod如果某个类型TU的子类型,则Foo[T]Foo[U]的子类型。 Great, we achieved covariance. 太好了,我们实现了协方差。 But note that I said "as long as someMethod is concerned". 但是请注意,我说的是“只要关注someMethod ”。 Foo is covariant in its type in this method, but in others it may be invariant or contravariant. Foo在此方法中其类型是协变的,但在其他方法中,它可能是不变的或相反的。

This kind of variance declaration is called use-site variance because we declare the variance of a type at the point of its usage (here it's used as a method parameter type of someMethod ). 这种差异声明被称为使用地点差异,因为我们在类型使用时声明类型的差异(在这里,它用作someMethod的方法参数类型)。 This is the only kind of variance declaration in, say, Java. 这是Java中唯一的一种方差声明。 When using use-site variance, you have watch out for the get-put principle (google it). 使用使用地点差异时,您要小心“ 获取量”原理 (用Google搜索)。 Basically this principle says that we can only get stuff from covariant classes (we can't put) and vice versa for contravariant classes (we can put but can't get). 基本上,这个原则说我们只能从协变类中获得东西(我们不能放进去),反之亦然(对于我们可以放进但不能得到)。 In our case, we can demonstrate it like this: 在我们的例子中,我们可以这样演示它:

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

More generally, we can only use covariant types as return types and contravariant types as parameter types. 更笼统地说,我们只能将协变类型用作返回类型,将反变量类型用作参数类型。

Now, use-site declaration is nice, but in Scala it's much more common to take advantage of declaration-site variance (something Java doesn't have). 现在,使用站点声明很不错,但是在Scala中,利用声明站点差异(Java所没有的东西)更为常见。 This means that we would describe the variance of Foo 's generic type at the point of defining Foo . 这意味着我们将在定义Foo描述Foo的泛型类型的变化。 We would simply say class Foo[+T] . 我们只说class Foo[+T] Now we don't need to use bounds when writing methods that work with Foo ; 现在,在编写与Foo一起使用的方法时,我们不需要使用界限; we proclaimed Foo to be permanently covariant in its type, in every use case and every scenario. 我们宣布Foo在每种使用案例和每种情况下,其类型都是永久协变的。

For more details about variance in Scala feel free to check out my blog post on this topic. 有关Scala中差异的更多详细信息,请随时查看我关于此主题的博客文章

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

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