148

この質問に続いて、誰かがScalaで次のことを説明できますか:

class Slot[+T] (var some: T) { 
   //  DOES NOT COMPILE 
   //  "COVARIANT parameter in CONTRAVARIANT position"

}

+T型宣言におけるとの違いを理解してTいます ( を使用するとコンパイルされますT)。しかし、それでは、パラメータ化されていないものを作成することに頼らずに、型パラメータが共変であるクラスを実際にどのように作成するのでしょか? 次のものを のインスタンスでのみ作成できるようにするにはどうすればよいTですか?

class Slot[+T] (var some: Object){    
  def get() = { some.asInstanceOf[T] }
}

編集-これを次のようにしました:

abstract class _Slot[+T, V <: T] (var some: V) {
    def getT() = { some }
}

これで問題ありませんが、必要な型パラメーターは 1 つだけですが、2 つの型パラメーターができました。次のように再質問します。

型が共変である不変 Slotクラスを作成するにはどうすればよいですか?

編集2:当たり前!私は使用varしていませんでしvalた。以下は私が欲しかったものです:

class Slot[+T] (val some: T) { 
}
4

4 に答える 4

304

一般に、変型パラメーターは、クラスがサブタイプ化されるにつれて変化することが許されるものです (または、サブタイプ化によって変化するため、"co-" 接頭辞が付けられます)。より具体的には:

trait List[+A]

List[Int]は のサブタイプであるList[AnyVal]ため、Intは のサブタイプですAnyVal。これはList[Int]、型の値List[AnyVal]が期待される場合のインスタンスを提供できることを意味します。これは、ジェネリックが機能するための非常に直感的な方法ですが、変更可能なデータが存在する場合に使用すると、適切ではない (型システムが壊れる) ことがわかります。これが、Java でジェネリックが不変である理由です。Java 配列 (誤って共変) を使用した不健全性の簡単な例:

Object[] arr = new Integer[1];
arr[0] = "Hello, there!";

Stringtype の値を typeの配列に代入しましたInteger[]。明らかな理由から、これは悪いニュースです。Java の型システムは、コンパイル時にこれを実際に許可します。JVM は実行時に "役立つ" をスローArrayStoreExceptionします。Scala の型システムは、Arrayクラスの型パラメーターが不変であるため (宣言は[A]ではなく[+A])、この問題を防ぎます。

分散として知られる別の種類の分散があることに注意してください。これは、共分散がいくつかの問題を引き起こす可能性がある理由を説明するため、非常に重要です。反変性は、文字どおり共分散の反対です。パラメーターは、サブタイプによって上方に変化します。非常に直感に反するため、部分的にあまり一般的ではありませんが、非常に重要なアプリケーションが 1 つあります。関数です。

trait Function1[-P, +R] {
  def apply(p: P): R
}

型パラメーターの " - " バリアンス アノテーションに注意してください。Pこの宣言は全体として、Function1が で反変でPで共変であることを意味しRます。したがって、次の公理を導き出すことができます。

T1' <: T1
T2 <: T2'
---------------------------------------- S-Fun
Function1[T1, T2] <: Function1[T1', T2']

T1'は のサブタイプ (または同じタイプ) である必要がありますが、とのT1場合は逆です。英語では、これは次のように読むことができます。T2T2'

Aのパラメーター型がBのパラメーター型のスーパータイプであり、Aの戻り値の型がBの戻り値の型のサブタイプである場合、関数Aは別の関数Bのサブタイプです。

この規則の理由は、読者への演習として残されています (ヒント: 上記の配列の例のように、関数がサブタイプ化されるさまざまなケースについて考えてみてください)。

共分散と反分散に関する新たな知識があれば、次の例がコンパイルされない理由を理解できるはずです。

trait List[+A] {
  def cons(hd: A): List[A]
}

問題は、関数がその型パラメーターがinvariantであることを期待してAいるのに対し、それは共変であることです。したがって、間違った方向に変化しています。興味深いことに、 を反変にすることでこの問題を解決できますが、関数は戻り値の型が共変であると想定しているため、戻り値の型は無効になります。consAListAList[A]cons

ここでの 2 つのオプションは、a)A不変にすること、共分散の優れた直感的なサブタイピング プロパティを失うこと、または b)下限としてcons定義するメソッドにローカル型パラメーターを追加することです。A

def cons[B >: A](v: B): List[B]

これで有効になりました。Aは下向きに変化していると想像できますが、下限があるためB上向きに変化する可能性があります。このメソッド宣言により、共変になることができ、すべてがうまくいきます。AAA

このトリックはList、特定性の低い type に特化したインスタンスを返す場合にのみ機能することに注意してくださいB。ミュータブルにしようとすると、最終的に type の値を typeの変数にList代入しようとすることになるため、事態は崩壊します。これはコンパイラによって許可されていません。可変性がある場合は常に、(アクセサーと共に) 不変性を意味する特定の型のメソッド パラメーターを必要とする、ある種のミューテーターが必要です。唯一可能な操作はアクセサーであり、アクセサーには共変の戻り値の型が与えられる可能性があるため、共分散は不変データで機能します。BA

于 2009-03-23T16:27:32.433 に答える
28

@ダニエルはそれを非常によく説明しています。しかし、それが許可されている場合、簡単に説明すると、次のようになります。

  class Slot[+T](var some: T) {
    def get: T = some   
  }

  val slot: Slot[Dog] = new Slot[Dog](new Dog)   
  val slot2: Slot[Animal] = slot  //because of co-variance 
  slot2.some = new Animal   //legal as some is a var
  slot.get ??

slot.getAnimalへの変換に失敗したため、実行時にエラーがスローされますDog(duh!)。

一般に、可変性は共分散と反分散ではうまくいきません。これが、すべての Java コレクションが不変である理由です。

于 2013-12-17T07:56:43.533 に答える
7

これについての完全な議論については、例によるScala 、57ページ以上を参照してください。

私があなたのコメントを正しく理解しているなら、56ページの下部から始まる一節を読み直す必要があります(基本的に、あなたが求めているのは、実行時チェックなしではタイプセーフではないと思いますが、scalaはそうしません、だからあなたは運が悪い)。あなたの構成を使用するために彼らの例を翻訳する:

val x = new Slot[String]("test") // Make a slot
val y: Slot[Any] = x             // Ok, 'cause String is a subtype of Any
y.set(new Rational(1, 2))        // Works, but now x.get() will blow up 

私があなたの質問を理解していないと感じた場合(明確な可能性)、問題の説明にさらに説明/コンテキストを追加してみてください。もう一度やり直します。

あなたの編集に応えて:不変のスロットはまったく異なる状況です...*笑顔*上記の例がお役に立てば幸いです。

于 2009-03-19T18:00:58.607 に答える
3

パラメータに下限を適用する必要があります。構文を覚えるのに苦労していますが、次のようになります。

class Slot[+T, V <: T](var some: V) {
  //blah
}

Scala-by-exampleは少し理解しにくいので、いくつかの具体的な例が役に立ちました。

于 2009-03-19T18:33:38.343 に答える