これは、型パラメーターが共変であるためです ( source )。
abstract class Either[+A, +B]
分散を説明する最も簡単な方法は、リストを使用することです。
abstract class MList[A]
case class Cons[A](head: A, tail: MList[A]) extends MList[A]
object MNil extends MList[Nothing]
そのようなリストをインスタンス化しようとすると、うまくいきません:
scala> Cons(1, MNil)
<console>:12: error: type mismatch;
found : MNil.type
required: MList[Int]
Note: Nothing <: Int (and MNil.type <: MList[Nothing]), but class MList is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
Cons(1, MNil)
^
エラー メッセージが示すように、問題は型A
が不変であることです。これは、型( )B
のサブタイプである型に対して、 と言うのは有効ではないことを意味します。A
B <: A
List[B] <: List[A]
A
共変を宣言することでこれを変更できます。
abstract class MList[+A]
// rest as before
scala> Cons(1, MNil)
res3: Cons[Int] = Cons(1,MNil$@5ee988c6)
共分散とは、型のサブタイプの関係が外側の型に転送されることを意味します。この例では、が Scala のボトム型であるMList[Nothing] <: MList[Int]
ためNothing
、可能なすべての型のサブタイプであることを意味します。
しかし今、問題があります。MList
type のパラメーターを期待するメソッドを追加することはできませんA
:
scala> :paste
// Entering paste mode (ctrl-D to finish)
abstract class MList[+A] {
def Cons(b: A) = new Cons(b, this)
}
case class Cons[A](head: A, tail: MList[A]) extends MList[A]
object MNil extends MList[Nothing]
// Exiting paste mode, now interpreting.
<console>:11: error: covariant type A occurs in invariant position in type (b: A)Cons[A] of method Cons
def Cons(b: A) = new Cons(b, this)
^
<console>:11: error: covariant type A occurs in contravariant position in type A of value b
def Cons(b: A) = new Cons(b, this)
^
コンパイラがこのコードを拒否する理由を説明するには、システムをもう少し深く掘り下げる必要があります。List[B] <: List[A]
の場合は常に真であると仮定しますB <: A
。次に、次のコードがコンパイルされます。
val xs: Array[Any] = Array[Int](18)
xs(0) = "hello"
なぜならInt <: Any
、それもまた真実であるからArray[Int] <: Array[Any]
です。また、これも真実でString <: Any
あるため、コンパイラはこのコードを問題なく変換できますが、実行時に失敗します。したがって、Scala では、aから an への代入は無効であり、コードは拒否されます。それにもかかわらず、エラーは発生しません。String
Array[Int]
Array[Int]
Array[Any]
List
scala> val xs: List[Any] = List[Int](18)
xs: List[Any] = List(18)
動作が異なる理由は、List
が共変でArray
あるのに対し、そうでない (不変) ためです。しかし、なぜ違いがあるのでしょうか。これは、 の不変の性質のため、List
a の要素をList
変更することはできません。しかしArray
、要素を変更することはできます。つまり、変数を使用すると、プログラマーは、要素が変更可能かどうか、およびコードで誰も愚かなことをしないように注意する必要があるという情報をコンパイラーに与えることができます。
MList
修正が必要な問題に戻ります。不変であるため、共変として安全に宣言できます。実際、そうしないと使えないので、そうする必要がありますMNil
。オブジェクトに型パラメーターを与えることはできないため、後で型の問題を回避するために、MList
可能な限り低い型で拡張する必要がありNothing
ます。この問題を解決するには、下限を設定する必要があります。
abstract class MList[+A] {
def Cons[B >: A](b: B) = new Cons(b, this)
}
case class Cons[A](head: A, tail: MList[A]) extends MList[A]
object MNil extends MList[Nothing]
scala> MNil Cons 2 Cons 1
res10: Cons[Int] = Cons(1,Cons(2,MNil$@2e6ef76f))
B >: A
は下限であり、手段A
は のサブタイプB
またはB
のスーパータイプですA
。(つまり) があるMList[Int]
ときにを取得するには、 と があるため、そのような下限が必要です。MList[Nothing]
MNil
Int >: Nothing
MList[Nothing].Cons[Int].Cons[Int]
そのため、そのjoinLeft
ような下限も必要です。実際、joinLeft
2 番目の型のバインドのみが必要であり、最初の型パラメーターで既に反変でB1 >: B
あるため、もう 1 つの型は必要ありません。<:<
これは、 on の下限にA
は何の効果もないことを意味します (補足: on の下限を削除するプル リクエストを開きましたA
が、後方ソース互換性が失われ、回避策が見つからなかったため、変更は拒否されました。 )