変性、上界、下界について
Techジェネリクス関連の概念、変性(variance)、上界(upper bound)、下界(lower bound)についての個人的なメモです。
参考文献
例について
クラスShapeがあり、そのサブクラスとしてクラスCircleやクラスRectがあるとする。Shapeの親クラスはObjectとし、ObjectのサブクラスとしてString等があるとする。
foo(x: Shape)
という関数にはCircleやRectを渡せるがStringは渡せないx: Shape
という変数にはCircleやRectを入れられるがStringは入れられない
変性(variance)
Array[Shape]
とArray[Circle]
の関係は?foo(items: Array[Circle])
にArray[Shape]
を渡してもよいか?- →これはだめ
foo(items: Array[Shape])
にArray[Circle]
を渡してもよいか?- →これは一見良さそうにも見えるが、foo内でitems.push(rect)とかしている可能性があるのでだめ
- ということでScalaでは普通にやるとこれは許可されない(非変、invariant)
- 逆にfoo内でitemsに書き込みしないなら渡してもよい。例えばImmutableArrayみたいなクラスを作ったとしたら?
- まとめると、コンテナクラス
G
があるとき、G[String]
とG[Object]
は一般には互換ではない- これを
G[String] <: G[Object]
とするのが共変指定(G[+A]
) - 逆に
G[Object] <: G[String]
とするのが反変指定(G[-A]
)
- これを
共変(covariant)
- (Scalaの場合) https://dwango.github.io/scala_text/type-parameter.html
class G[+A]
のように+
を付けると、G[Object]
を受け取る関数にG[String]
を渡すことが許可される+
を付けたのに書き換えとかしようとした場合はコンパイルエラーになる
class G[+T](var x: T) {
def set(newX: T) { x = newX }
}
def foo(items: G[Object]) { }
val items = new G[String]("foo")
foo(items)
// (略)/a.scala:20: error: covariant type T occurs in contravariant position in type T of value x_=
// class G[+T](var x: T) {
// ^
// (略)/a.scala:21: error: covariant type T occurs in contravariant position in type T of value newX
// def set(newX: T) { x = newX }
反変(contravariant)
共変の逆(G[Object]
<: G[String]
)
Action<object> objAction = x => { Console.Write(x); };
Action<string> strAction = objAction;
- 共変:「このコンテナは書き換えを行わないので大丈夫です」
- 反変: 「このコンテナに入れたもは取り出さないので大丈夫です」
declaration-site vs. use-site variance
- https://docs.scala-lang.org/tour/variances.html Comparison With Other Languages
- Java, Kotlin, C#ではコンテナ型を使う箇所に変位指定(in/outみたいな)を書ける
name | decl-site | use-site |
---|---|---|
Java | no | yes |
Scala | yes | no |
C# | yes | yes |
Kotlin | yes | yes |
Swift | no | no |
- In Swift, some buildins like
Array
has variance https://medium.com/@aunnnn/covariance-and-contravariance-in-swift-32f3be8610b9
境界(bounds)
上界(upper bound)
T <: A
のとき、T型の値に対してAのメソッドを呼べる
下界(lower bound)
- Javaとかにはない
- 共変と組み合わせて使う https://dwango.github.io/scala_text/type-parameter.html
- 「このコンテナは書き換えを行うが、最初に入れたものと同じクラス(かスーパークラス)のオブジェクトしか入れないので大丈夫です」という指定
- cf. 共変のみを指定した場合は「このコンテナは書き換えを行わないので大丈夫です」という指定になる
メモ: Scalaの変性チェック
共変に指定したとき、どこまでが許されるのか。
Scalaは結構厳しくて(v2.12.2)、外部からTを受け取ることをそもそも許さないようになっていた。immutableなコンテナならTを受け取らなくてもいいよね?ってことか。
class G[+T]() {
def foo(newX: T) { }
}
//prog.scala:2: error: covariant type T occurs in contravariant position in type T of value newX
// def foo(newX: T) { }