web-dev-qa-db-ja.com

サンプルがコンパイルされないのはなぜですか?

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

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

}

+Tおよび型宣言のTT)。しかし、実際には、物unparametrizedを作成せずに、その型パラメーターで共変なクラスを実際にどのように書くのでしょうか? Tのインスタンスでのみ以下を作成できるようにするにはどうすればよいですか?

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

[〜#〜] edit [〜#〜]-これは次のようになりました:

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

これはすべて良いですが、今は2つの型パラメーターがあり、1つだけが必要です。このように質問を再質問します。

immutableSlotクラスを書くにはどうすればcovariantそのタイプ?

EDIT 2:当たり前! varではなくvalを使用しました。以下は私が欲しかったものです:

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

一般的に、covariant typeパラメーターは、クラスがサブタイプ化されるにつれて変化することを許可されるパラメーターです(あるいは、サブタイピングによって変化するため、「co-」プレフィックス)。より具体的に:

trait List[+A]

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

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

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

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

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

P型パラメーターの "-"差異注釈に注意してください。この宣言全体は、Function1Pで反変であり、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]
}

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

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

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

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

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

298
Daniel Spiewak

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

  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は、AnimalからDogへの変換に失敗したため、実行時にエラーをスローします(ダァ!)。

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

27
Jatin

詳細については、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 

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

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

7
MarkusQ

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

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

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

3
Saem