web-dev-qa-db-ja.com

scala-ジェネリックのAny対アンダースコア

Scalaの次のGenerics定義の違いは何ですか:

class Foo[T <: List[_]]

そして

class Bar[T <: List[Any]]

私の腸は、それらはほぼ同じであるが、後者はより明確であることを教えてくれます。前者はコンパイルするが、後者はコンパイルしないが、正確な違いに指を当てることができない場合を見つけています。

ありがとう!

編集:

別のものをミックスに投入できますか?

class Baz[T <: List[_ <: Any]]
65
Sean Connolly

OK、コメントを投稿するのではなく、自分の意見を取り入れるべきだと思いました。 TL; DRを最後までスキップする場合、これは長くなります。

ランドール・シュルツが言ったように、ここで___は実在型の省略形です。すなわち、

_class Foo[T <: List[_]]
_

の短縮形です

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

ランドール・シュルツの答えが述べていることに反して(完全な開示:この投稿の以前のバージョンでも間違っていました。指摘してくれたJesper Nordenbergに感謝します)、これは次とは異なります:

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

また、次と同じでもありません:

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

気をつけて、それを間違えるのは簡単です(私の以前の愚かさからわかるように):Randall Shulzの答えが参照する記事の著者は、自分でそれを間違え(コメントを参照)、後で修正しました。この記事の私の主な問題は、示されている例では、存在の使用がタイピングの問題から私たちを救うことになっているが、そうではないということです。コードを確認し、compileAndRun(helloWorldVM("Test"))またはcompileAndRun(intVM(42))をコンパイルしてください。うん、コンパイルしません。 compileAndRunで単にAをジェネリックにすると、コードがコンパイルされ、はるかに簡単になります。要するに、それはおそらく実存とそれらが何のためにあるのかを学ぶのに最適な記事ではありません(著者自身が記事で「整理する必要がある」とコメントで認めています)。

したがって、この記事を読むことをお勧めします。 http://www.artima.com/scalazine/articles/scalas_type_system.html 、特に「Existential types」および「Variance in JavaおよびScala」。

この記事から得られる重要なポイントは、非共変型を扱う場合に、存在が有用であることです(汎用Javaクラスを扱うことができることは別として)。以下に例を示します。

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

このクラスはジェネリックです(不変でもあります)が、helloは(getNameとは異なり)typeパラメーターを実際に使用していないことがわかります。したがって、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))
_

素晴らしい、これは動作します。ここで実存も使用できます。

_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は不変であるため、最後の行はコンパイルされません。したがって、StringSymbolの両方がAnyを拡張していても、_Greets[String]_と_Greets[Symbol]_を_Greets[Any]_として扱うことはできません。

OK、簡略表記___を使用して、実存で試してみましょう。

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

これは正常にコンパイルされ、期待どおりに実行できます。

_greetsSet foreach (_.hello)
_

ここで、最初に型チェックの問題が発生した理由は、Greetsが不変であるためであることを思い出してください。共変クラス(_class Greets[+T]_)に変換された場合、すべてがそのまま使用でき、実存性は必要ありませんでした。


要約すると、存在はジェネリック不変クラスを扱うのに役立ちますが、ジェネリッククラスが別のジェネリッククラスの型パラメーターとして表示される必要がない場合は、存在を必要とせず、単純に型パラメーターを追加する可能性がありますあなたの方法に動作します

今、あなたの特定の質問に戻って(ついに、私は知っています!)

_class Foo[T <: List[_]]
_

Listは共変であるため、これはすべての意図および目的のために、単に言っているのと同じです。

_class Foo[T <: List[Any]]
_

したがって、この場合、どちらの表記法を使用するかは、実際にはスタイルの問題です。

ただし、ListSetに置き換えると、状況が変わります。

_class Foo[T <: Set[_]]
_

Setは不変であるため、この例のGreetsクラスと同じ状況になります。したがって、上記は実際に非常に異なっています

_class Foo[T <: Set[Any]]
_
88

前者は、コードが型が何であるかを知る必要がなく、制約する必要がない場合の実在型の省略形です。

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

この形式は、Listの要素タイプがclass Fooではなく、Listの要素タイプがAnyであることを具体的に示します。

この簡潔な説明をご覧ください ブログ記事 Scala([〜#〜] edit [〜#〜]:このリンクは現在無効です。スナップショットは archive.org )で入手できます

6
Randall Schulz