14

下限を使用して新しい不変型を作成する方法に関して、共分散について頭を悩ませようとしています

class ImmutableArray[+T](item: T, existing: List[T] = Nil) {  
  private val items = item :: existing

  def append[S >: T](value: S) = new ImmutableArray[S](value, items)
}

type パラメーターTはルールに違反しているため、append メソッドでは使用できないことを理解していますが、Customerクラスとサブクラスがある場合Studentでも、 type を作成できますU Student

これが機能することはわかりますが、なぜこれが規則違反にならないのですか? Students のリストを持っていて a を追加したかどうかは理解できましたが、親タイプであるため a に a を割り当てることができないため、s のCustomerリストしか返すことができませんでした。しかし、なぜ使用できるのでしょうか?CustomerCustomerStudentStudent

私は何が欠けていますか?

ありがとうブレア

4

4 に答える 4

12

最初の質問:

型パラメータ T はルールに違反するため、append メソッドで使用できないことがわかりました

まぁ使える。S >: T単純に、S等しい型Tまたはその親である型を渡すと、それSが使用されることを意味します。サブレベルの型を渡すと、TそれTが使用されます。

scala> class Animal
defined class Animal

scala> class Canine extends Animal
defined class Canine

scala> class Dog extends Canine
defined class Dog

scala> new ImmutableArray[Canine](new Canine)
res6: ImmutableArray[Canine] = ImmutableArray@a47775

scala> res6.append(new Animal)
res7: ImmutableArray[Animal] = ImmutableArray@1ba06f1

scala> res6.append(new Canine)
res8: ImmutableArray[Canine] = ImmutableArray@17e4626

scala> res6.append(new Dog)
res9: ImmutableArray[Canine] = ImmutableArray@a732f0

上記の方法でres6.append(new Dog)も、タイプ Canine の ImmutableArray が得られます。犬を犬の配列に追加しても犬の配列は維持されるため、ある意味で考えると完全に理にかなっています。しかし、Animal を Canine Array に追加すると、完全に犬のようにはならないため、Animal になります (大臼歯か何かになる可能性があります)。

これは、反変型宣言が書き込み (あなたのケース) と読み取りの共分散に最適であることが通常知られている理由の完璧な例です。

あなたの例では、 (Javaの世界から)と比較S >: Tしているため、混乱が生じる可能性があると思います。のスーパークラスである引数の型を持つようにバインドされており、S super Tサブタイプの引数を に渡すことはできません。scala では、コンパイラがこれを処理します (型推論のおかげです)。S super TTT

于 2013-10-23T17:59:47.020 に答える
4

次の階層を考慮してください。

class Foo
class Bar extends Foo { def bar = () }
class Baz extends Bar { def baz = () }

そしてあなたに似たクラス:

class Cov[+T](val item: T, val existing: List[T] = Nil) {
  def append[S >: T](value: S) = new Cov[S](value, item :: existing)
}

Foo次に、サブタイプごとに 3 つのインスタンスを作成できます。

val cFoo = new Cov(new Foo)
val cBar = new Cov(new Bar)
val cBaz = new Cov(new Baz)

barそして、要素を必要とするテスト関数:

def test(c: Cov[Bar]) = c.item.bar

それは保持します:

test(cFoo) // not possible (otherwise `bar` would produce a problem)
test(cBaz) // ok, since T covariant, Baz <: Bar --> Cov[Baz] <: Cov[Bar]; Baz has bar

appendメソッドは、上限に戻ります。

val cFoo2 = cBar.append(new Foo)

Foo >: Bar, List[Foo] >: List[Bar], Cov[Foo] >: Cov[Bar]. _

これで、正しくbarアクセスできました:

cFoo2.item.bar // bar is not a member of Foo

上限が必要な理由を理解するために、次のことが可能だったと想像してください

class Cov[+T](val item: T, val existing: List[T] = Nil) {
  def append(value: T) = new Cov[T](value, item :: existing)
}

class BarCov extends Cov[Bar](new Bar) {
  override def append(value: Bar) = {
    value.bar // !
    super.append(value)
  }
}

それからあなたは書くことができます

def test2[T](cov: Cov[T], elem: T): Cov[T] = cov.append(elem)

また、次の違法行為が許可されます。

test2[Foo](new BarCov, new Foo) // BarCov <: Cov[Foo]

value.bar呼び出される場所Foo。(正しく) 上限を使用するとappend、仮説上の最後の例のように実装することはできません。

class BarCov extends Cov[Bar](new Bar) {
  override def append[S >: Bar](value: S) = {
    value.bar // error: value bar is not a member of type parameter S
    super.append(value)
  }
}

したがって、型システムは健全なままです。

于 2013-10-23T17:45:26.487 に答える