ジェネリクス: Kotlin / Scala
@main
def main(): Unit = {
val h: Holder[Int] = Holder(1)
val v: Int = h.get()
h.set(2)
}
case class Holder[T](value: T) {
def get(): T = value
def set(t: T): Unit = {}
}
Scala も不変
val h: Holder[Animal] = Holder[Dog](new Dog())
はできない
メソッド引数で共変や
def main(): Unit = {
f(Holder[Dog](new Dog()))
}
def f(h: Holder[_ <: Animal]): Unit = {}
反変も定義可能
def f(h: Holder[_ >: Animal]): Unit = {}
+
でクラスの変性を共変にできる
しかしクラスメソッドに T
を引数にするものは定義できない
@main
def main(): Unit = {
val h: CoHolder[Animal] = CoHolder[Dog](new Dog())
println(h)
val a: Animal = h.get()
println(a)
}
case class CoHolder[+T](value: T) {
def get(): T = value
// def set(t: T): Unit = {} // Covariant type T occurs in contravariant position in type T of value t
}
-
でクラスの変性を反変にできる
しかしクラスメソッドに T
を返すものは定義できない
@main
def main(): Unit = {
val h: ContraHolder[Dog] = ContraHolder[Animal]()
h.set(new Dog())
}
case class ContraHolder[-T]() {
// def get(): T = value // Contravariant type T occurs in covariant position in type T of value value
def set(t: T): Unit = {}
}
どういうときに使われているか
Kotlin も不変
fun main(args: Array<String>) {
val h: Holder<Dog> = Holder<Dog>(Dog())
}
data class Holder<T>(val value: T) {
fun get(): T = value
fun set(t: T) = Unit
}
interface Animal
class Dog : Animal
はできても
val h: Holder<Animal> = Holder<Dog>(Dog())
はできない
Scala の +
のように out
でクラス定義時に変性を共変にできる
fun main(args: Array<String>) {
val h: CoHolder<Animal> = CoHolder<Dog>(Dog())
}
data class CoHolder<out T>(val value: T) {
fun get(): T = value
// fun set(t: T) = Unit // Type parameter T is declared as 'out' but occurs in 'in' position in type T
}
Scala の -
のように in
でクラス定義時に変性を反変にできる
fun main(args: Array<String>) {
val h: ContraHolder<Dog> = ContraHolder<Animal>()
}
class ContraHolder<in T> {
// fun get(): T = value // Type parameter T is declared as 'in' but occurs in 'out' position in type T
fun set(t: T) = Unit
}
メソッド引数で変性を指定することも可能
fun main(args: Array<String>) {
f1(Holder<Dog>(Dog()))
f2(Holder<Animal>(Animal()))
}
fun f1(h: Holder<out Animal>) = Unit
fun f2(h: Holder<in Dog>) = Unit
たとえば Kotlin の List
は List<out T>
になっているので最初から共変
Kotlin の List
は immutable なので set
などが実装されていないし、共変にしておく方が使いやすい
fun main(args: Array<String>) {
val l1: List<Number> = listOf<Int>(1, 2, 3)
val animals: List<Animal> = listOf(Dog())
f(animals)
}
fun f(animals: List<Animal>) = Unit
MutableList
は set
できないと困るので MutableList<T>
、よって不変
val l2: MutableList<Number> = mutableListOf<Int>(1, 2, 3) // エラー
そういう目線で標準ライブラリをみて回るとおもしろいかも
public interface List<out E> : Collection<E>
public interface Collection<out E> : Iterable<E>
public interface Iterable<out T>
public interface MutableList<E> : List<E>, MutableCollection<E>
public interface MutableCollection<E> : Collection<E>, MutableIterable<E>
public interface MutableIterable<out T> : Iterable<T>
メソッドを添えて図にすると面白いはず
Java は変性うんぬんの前に Collection
に add
が定義されちゃってるから辛いところあるよね
in T
の例
Comparator
は in T
である ( Comparator<T>
は java へのエイリアスだが、java の Comparator<T>
は特別扱いで反変 )
fun main(args: Array<String>) {
val dogs: List<Dog> = listOf(Dog("Alice"), Dog("Bob"))
val cats: List<Cat> = listOf(Cat("Christina"), Cat("David"))
val cmp = Comparator<Animal> { a1, a2 -> a1.name.length - a2.name.length }
println(dogs.sortedWith(cmp)) // Bob, Alice
println(cats.sortedWith(cmp)) // David, Christina
}
sortedWith
の引数は Comparator<in T>
反変だと List<Dog>
に List<Animal>
が渡せたように、List<Dog>
の sortedWith
に Comparator<Animal>
が渡せる
List<Int>
の sortedWith
に Comparator<Number>
を使える
List<Double>
の sortedWith
にも Comparator<Number>
を使える
反変でなく不変だと、Animal
でソートしたいだけなのに List<Dog>
と List<Cat>
それぞれに Comparator<Dog>
と Comparator<Cat>
を用意する必要がある