1. ホーム
  2. scala

scala - GenericsにおけるAnyとUnderscoreの比較

2023-08-30 18:43:02

質問

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の回答で参照された記事の著者は自分でも間違えていて(コメント参照)、後で修正したそうなので、注意してください。この記事の主な問題は、示されている例では、存在詞の使用によって型付けの問題から解放されるはずなのに、そうなっていないことです。コードをチェックして、コンパイルしてみてください。 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 とは異なり)型パラメータを全く使っていません。 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] としても StringSymbol はどちらも Any .

では、実存的なものを使って、省略記法でやってみましょう。 _ :

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]]