Open14

ジェネリクス: 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 = {}
}
ほげさんほげさん

メソッド引数で共変や

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 の ListList<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

MutableListset できないと困るので 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 は変性うんぬんの前に Collectionadd が定義されちゃってるから辛いところあるよね

ほげさんほげさん

in T の例

Comparatorin 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>sortedWithComparator<Animal> が渡せる

List<Int>sortedWithComparator<Number> を使える
List<Double>sortedWith にも Comparator<Number> を使える

反変でなく不変だと、Animal でソートしたいだけなのに List<Dog>List<Cat> それぞれに Comparator<Dog>Comparator<Cat> を用意する必要がある