(co-、contra-、in-)分散がどのように機能するのか、例ではコンパイルできないのはなぜですか?


147

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

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

}

型宣言の+Tとの違いを理解してTいます(を使用するとコンパイルされますT)。しかし、その後どのように一つは、実際のものを作成するに頼ることなく、その型パラメータで共変であるクラス記述んunparametrizedを?以下がのインスタンスでのみ作成できることをどのように確認できますTか?

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

編集 -これは次のようになりました:

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

これで十分ですが、今では2つの型パラメーターがあり、1つだけ必要です。私はこうして質問を再質問します:

型が共変である不変 Slotクラスをどのように書くことができますか?

編集2:ああ!私は使用varしましたval。以下は私が欲しかったものです:

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

6
なぜならvar、そうでvalはないのに設定可能だからです。scalaの不変コレクションが共変であるのと同じ理由ですが、可変コレクションはそうではありません。
oxbow_lakes 2009年

これは、このコンテキストでは興味深いかもしれません:scala-lang.org/old/node/129
user573215

回答:


302

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

trait List[+A]

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

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

typeの値をtype Stringの配列に割り当てましたInteger[]。明らかなはずの理由で、これは悪いニュースです。Javaの型システムでは、コンパイル時にこれを実際に許可しています。JVMはArrayStoreException実行時に「便利に」スローされます。Scalaの型システムは、Arrayクラスの型パラメーターが不変であるため(宣言では[A]なく[+A])、この問題を回避します。

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

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'はのサブタイプ(または同じタイプ)である必要がありますが、and T1の反対です。英語では、これは次のように読むことができます。T2T2'

関数Aは、他の機能のサブタイプであるBのパラメータタイプ場合Aは、のパラメータの型のスーパータイプであるBの戻り型ながら、Aは、の戻り型のサブタイプであるB

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

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

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

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

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

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

これは現在有効です。あなたはそれAが下向きに変化していると想像することがBできますが、それはその下限なAので、上向きに変化することができますA。このメソッド宣言を使用するとA、共変にすることができ、すべてが機能します。

このトリックListは、あまり具体的でない型に特化したインスタンスを返す場合にのみ機能することに注意してくださいB。あなたが作るしようとするList可変、物事はあなたがタイプの割り当て値にしようとしてしまうので、ブレークダウンB型の変数にAコンパイラによって許可されていません。ミュータビリティがある場合は常に、ある種のミューテーターが必要です。ミューテーターには、特定のタイプのメソッドパラメーターが必要です。これは、(アクセサーと共に)不変性を意味します。共変は不変データで機能します。可能な操作はアクセサーのみであり、これには共変の戻り型が指定される場合があるためです。


4
これは平易な英語で次のように述べられますか?パラメータとしてより単純なものを取り、より複雑なものを返すことができますか?
Phil

1
Javaコンパイラ(1.7.0)は「Object [] arr = new int [1];」をコンパイルしません。しかし、エラーメッセージ「java:互換性のないタイプが必要です:java.lang.Object []が見つかりました:int []」を返します。「Object [] arr = new Integer [1];」という意味ですか。
EmreSevinç2013年

2
あなたが言及したとき、「このルールの理由は読者への演習に任されています(ヒント:上記の私の配列の例のように、関数がサブタイプ化されているので、さまざまなケースについて考えてください)。」実際にいくつか例を挙げていただけますか?
perryzheng 14

2
あたり@perryzheng これは、取るtrait Animaltrait Cow extends Animaldef iNeedACowHerder(herder: Cow => Unit, c: Cow) = herder(c)def iNeedAnAnimalHerder(herder: Animal => Unit, a: Animal) = herder(a)。次に、iNeedACowHerder({ a: Animal => println("I can herd any animal, including cows") }, new Cow {})大丈夫です。私たちの動物の飼い主は牛を飼うことができ iNeedAnAnimalHerder({ c: Cow => println("I can herd only cows, not any animal") }, new Animal {})ますが、私たちの牛の飼い主はすべての動物を飼うことができないため、コンパイルエラーが発生します。
Lasf

これは関連があり、分散について私を助けました:typelevel.org/blog/2016/02/04/variance-and-functors.html
Peter Schmitz

27

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

  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.get次に、(AnimalDog)への変換に失敗したため、実行時にエラーがスローされます。

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


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 

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

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


私はそれを読みました。残念ながら、私は(まだ)上記の質問の方法(つまり、実際にTでパラメーター化されたクラスの共変量を書き込む方法)を理解できません
oxbow_lakes 2009年

これが少し厳しいと気付いたので、ダウンマークを削除しました。私は、例としてScalaからビットを読み取ったことを質問で明確にすべきでした。「形式に
とらわれ

@oxbow_lakes smile Scala By Example あまり正式な説明ではないことを恐れています。せいぜい、ここでは具体的な例を使用して作業を試みることができます...
MarkusQ '19 / 03/09

すみません-スロットを変更可能にしたくありません。私は問題がvalではなくvarと宣言されていることに気づきました
oxbow_lakes 2009年

3

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

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

Scala-by-exampleは理解するのが少し難しいですが、いくつかの具体的な例が役立つでしょう。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.