簡體   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