2013-03-03 60 views
49

下劃線什麼是Scala以下泛型定義之間的不同:斯卡拉 - 任何VS仿製藥

class Foo[T <: List[_]] 

class Bar[T <: List[Any]] 

我的直覺告訴我,他們是差不多的,但後者更明確。我發現前者編譯但後者不編譯的情況,但不能把我的手指放在確切的區別上。

謝謝!

編輯:

我可以拋出另一個混進去?

class Baz[T <: List[_ <: Any]] 
+5

將類型約束爲'<:Any'永遠不會改變任何東西。 Scala中的每個類型都是<<:Any'。 – 2013-03-03 19:23:35

回答

64

好的,我想我應該把它拿起來,而不是僅僅發表評論。對不起,如果你想讓TLDR跳轉到最後,這將會很長。

正如Randall Schulz所說,這裏的_是存在型的簡寫。也就是說,

class Foo[T <: List[_]] 

class Foo[T <: List[Z] forSome { type Z }] 

注意的簡寫是相反的是蘭德爾Shulz的回答中提到(全面披露:我聽錯了,太早期版本FO這個帖子,感謝加斯帕Nordenberg爲它指向了)這種不一樣:

class Foo[T <: List[Z]] forSome { type Z } 

也不是一樣的:

class Foo[T <: List[Z forSome { type Z }] 

要小心,很容易弄錯它(正如我之前的愚蠢表演):Randall Shulz的回答引用的文章的作者自己弄錯了(見評論),並在稍後修復。這篇文章的主要問題是,在所示的例子中,使用existentials應該可以讓我們免於打字問題,但事實並非如此。去檢查代碼,並嘗試編譯compileAndRun(helloWorldVM("Test"))compileAndRun(intVM(42))。是的,不編譯。只需在A中編寫compileAndRun就可以編譯代碼,並且它會簡單得多。 總之,這可能不是最好的文章來了解存在和它們的優點(作者本人在評論中承認文章「需要整理」)。

所以我寧願推薦閱讀這篇文章:http://www.artima.com/scalazine/articles/scalas_type_system.html,特別是名爲「存在類型」和「Java和Scala中的差異」的章節。

你應該從這篇文章中得到的重要一點是,在處理非協變類型時,存在是有用的(除了能夠處理泛型java類)。 這裏是一個例子。

case class Greets[T](private val name: T) { 
    def hello() { println("Hello " + name) } 
    def getName: T = name 
} 

這個類是通用的(注意也即是不變的),但我們可以看到,hello真的不使用類型參數的(不像getName),所以如果我得到Greets我的一個實例應該始終能夠調用它,無論是T是。如果我想定義需要Greets實例的方法,只是調用其hello方法,我可以試試這個:

def sayHi1(g: Greets[T]) { g.hello() } // Does not compile 

果然,這不能編譯,因爲T出來的無處這裏。然後

OK,讓我們的方法一般:

def sayHi2[T](g: Greets[T]) { g.hello() } 
sayHi2(Greets("John")) 
sayHi2(Greets('Jack)) 

大,這個工程。我們也可以在這裏使用existentials:

def sayHi3(g: Greets[_]) { g.hello() } 
sayHi3(Greets("John")) 
sayHi3(Greets('Jack)) 

也可以。因此,總而言之,在類型參數(如sayHi2)中使用存在(如sayHi3)沒有真正的好處。

但是,如果Greets本身作爲另一個泛型類的類型參數出現,則會發生變化。舉例來說,我們想要在列表中存儲Greets(不同的T)的多個實例。讓我們試一下:

val greets1: Greets[String] = Greets("John") 
val greets2: Greets[Symbol] = Greets('Jack) 
val greetsList1: List[Greets[Any]] = List(greets1, greets2) // Does not compile 

最後一行不編譯,因爲Greets是不變的,所以Greets[String]Greets[Symbol]不能作爲Greets[Any]即使StringSymbol兩個延伸Any處理。

OK,讓我們嘗試用一個存在,用速記符號_

val greetsList2: List[Greets[_]] = List(greets1, greets2) // Compiles fine, yeah 

編譯沒有問題,你可以做,如預期:

greetsSet foreach (_.hello) 

現在,請記住原因首先我們有一個類型檢查問題,因爲Greets是不變的。如果它變成了一個協變類(class Greets[+T]),那麼所有東西都可以在盒子裏運行,我們永遠不會需要存在。


所以總結起來,existentials是有用的處理一般不變類,但如果通用類並不需要單獨出現的類型參數到另一個泛型類,有機會,你不需要existentials和簡單地增加一個類型參數的方法,將工作

現在回來(在最後,我知道!)你的具體問題,關於

class Foo[T <: List[_]] 

因爲List是協變的,這是所有意圖和purp其他只是說:

class Foo[T <: List[Any]] 

所以在這種情況下,使用任何符號實際上只是一個風格問題。

但是,如果你Set替換List,事情的變化:

class Foo[T <: Set[_]] 

Set是不變的,因此我們在相同的情況下從我的例子Greets類。因此,上述確實與

有很大不同
class Foo[T <: Set[Any]] 
+0

不,類Foo [T <:List [_]]'是類Foo [T <:List [Z] forSome {type Z}]'的縮寫。 'List [Greets [_]]'是'List [Greets [Z] forSome {type Z}]'(而不是'List [Greets [Z]] for some {type Z}'')的簡寫。 – 2013-03-06 11:58:02

+0

Doh,傻我!謝謝,我已經解決了這個問題。有趣的是,我的第一個嘗試是檢查David R MacIver的文章(http://www.drmaciver.com/2008/03/existential-types-in-scala/),它精確地談論了存在速記,並警告說他們不直觀的desugaring。事情是他自己看起來錯了。實際上,發生的事情是,在他的文章(scala 2.7.1,請參閱http://www.scala-lang.org/node/43#2.8.0上的更改日誌)後不久,desugaring的方式發生了變化。我想這個改變會導致混亂。 – 2013-03-06 13:10:18

+0

好吧,很容易混淆存在類型語法的含義。至少目前的desugaring是最符合邏輯的恕我直言。 – 2013-03-06 14:03:43

6

前者是一個存在的類型的速記時的代碼並不需要知道是什麼類型或限制它:

class Foo[T <: List[Z forSome { type Z }] 

這種形式說的List元素類型未知的class Foo而不是你的第二種形式,具體說明List的元素類型是Any

查看關於Scala存在類型的簡短說明blog article

+3

當你解釋什麼是存在的時候,我認爲這個問題不是一般存在的問題,而是存在'T [_]'(這是存在使用的特殊情況)和'T [Any] '? – 2013-03-03 15:58:18

+0

當然有。我提到的博客有一個很好的例子。 – 2013-03-03 17:30:52

+3

我寧願讓你的**答案提到協方差對於'T [_]'和'T [Any]'之間的區別是如何必不可少的,因爲它是問題的核心「爲什麼要用'T [_]'在T [Any]'上方''「。另外,在他的問題中,肖恩·康諾利明確提到了「列表[_]」。鑑於'List'實際上是協變的,人們可能會懷疑在這種情況下**是否真的存在'List [_]'和'List [Any]之間的區別。 – 2013-03-03 18:22:09