Scala での次の Generics 定義の違いは何ですか:
class Foo[T <: List[_]]
と
class Bar[T <: List[Any]]
私の直感によると、それらはほぼ同じですが、後者の方がより明確です。前者はコンパイルできるが後者はコンパイルできないケースを見つけていますが、正確な違いを特定することはできません。
ありがとう!
編集:
ミックスに別のものを投入できますか?
class Baz[T <: List[_ <: Any]]
Scala での次の Generics 定義の違いは何ですか:
class Foo[T <: List[_]]
と
class Bar[T <: List[Any]]
私の直感によると、それらはほぼ同じですが、後者の方がより明確です。前者はコンパイルできるが後者はコンパイルできないケースを見つけていますが、正確な違いを特定することはできません。
ありがとう!
編集:
ミックスに別のものを投入できますか?
class Baz[T <: List[_ <: Any]]
OK、コメントを投稿するだけでなく、自分の意見を述べるべきだと思いました。申し訳ありませんが、これは長くなります。TL;DR が必要な場合は、最後までスキップしてください。
Randall Schulz が言ったように、これ_
は実存型の略記です。すなわち、
class Foo[T <: List[_]]
の省略形です
class Foo[T <: List[Z] forSome { type Z }]
Randall Shulzの回答が言及していることとは反対に(完全な開示:この投稿の以前のバージョンでも間違っていました。指摘してくれたJesper 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))
。はい、コンパイルされません。単純にcompileAndRun
ジェネリックにするだけA
でコードがコンパイルされ、はるかに簡単になります。要するに、それはおそらく実存主義とその利点について学ぶのに最適な記事ではありません (著者自身がコメントで、この記事には「片付けが必要」であることを認めています)。
したがって、この記事を読むことをお勧めします: http://www.artima.com/scalazine/articles/scalas_type_system.html、特に「Existential types」と「Variance in Java and Scala」という名前のセクション。
この記事から得られる重要な点は、非共変型を扱う場合に (汎用 Java クラスを処理できることを除けば) 存在論が役立つということです。ここに例があります。
case class Greets[T]( private val name: T ) {
def hello() { println("Hello " + name) }
def getName: T = name
}
hello
このクラスはジェネリックです ( is invariant であることに注意してください) が、実際には型パラメーターをまったく使用しないことがわかります ( とは異なり) getName
。は。インスタンスを受け取り、そのメソッドを呼び出すだけのメソッドを定義したい場合は、次のようにします。Greets
T
Greets
hello
def sayHi1( g: Greets[T] ) { g.hello() } // Does not compile
案の定、これはT
どこからともなく出てくるように、コンパイルされません。
それでは、メソッドをジェネリックにしましょう。
def sayHi2[T]( g: Greets[T] ) { g.hello() }
sayHi2( Greets("John"))
sayHi2( Greets('Jack))
素晴らしい、これはうまくいきます。ここで存在を使用することもできます。
def sayHi3( g: Greets[_] ) { g.hello() }
sayHi3( Greets("John"))
sayHi3( Greets('Jack))
あまりにも動作します。したがって、全体として、( のようにsayHi3
) 型パラメーターよりも存在 (のように) を使用しても、ここでは実際の利点はありませんsayHi2
。
ただし、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]
String
Symbol
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
、これはすべての意図と目的において、次のように言っているのと同じです。
class Foo[T <: List[Any]]
したがって、この場合、どちらの表記法を使用するかは、実際にはスタイルの問題です。
ただし、 に置き換えるList
とSet
、状況が変わります。
class Foo[T <: Set[_]]
Set
Greets
は不変なので、私の例のクラスと同じ状況になります。したがって、上記は実際には非常に異なります
class Foo[T <: Set[Any]]
前者は、コードが型を認識したり制約したりする必要がない場合の、存在型の省略形です。
class Foo[T <: List[Z forSome { type Z }]]
このフォームは、 の要素タイプがであると具体的に述べている 2 番目のフォームではなく、 の要素タイプList
が不明であることを示しています。class Foo
List
Any
ScalaのExistential Typesに関するこの簡単な説明ブログ記事をチェックしてください(編集:このリンクは現在無効です。スナップショットはarchive.orgで入手できます)