ex-mixi Advent Calendar 2017 の4日目、kzys です。

株式会社ミクシィには2008年に新卒で入社して、2012年の夏ごろまで働いていました。その後は、目黒のアマゾンジャパンで2年くらいモバイルまわりの開発をしてから、シアトルの Amazon.com に異動し、最近は Alexa のバックエンド側の開発をしています。

Alexa について興味があるひとは是非 @AlexaDevsJP をフォローしてください! ということで、今回は仕事とは関係なく Kotlin の話をします。本記事は個人の意見であり、所属する組織の見解とは関係ありません。

はじめに

Kotlin のコレクションライブラリは、型レベルで mutable / immutable を区別しながらも、その実装のほとんどを Java 標準のコレクションライブラリでまかなうという面白い特徴があります。

ここでは、その実装について、Kotlin の標準ライブラリとコンパイラのソースコードを追いながら、解説していこうと思います。

標準ライブラリ

例えば listOf("foo", "bar", "baz") というコードを実行するとします。

この場合、まず kotlin.collections.listOf() が呼ばれます。

public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()

Kotlin における可変長引数は配列として渡ってくるので elements.asList()Array#asList() です。

/**
 * Returns a [List] that wraps the original array.
 */
public fun <T> Array<out T>.asList(): List<T> {
    return ArraysUtilJVM.asList(this)
}

これは ArraysUtilJVM を呼んでいるだけのようです。ここまでのコードは全て Kotlin で書かれていましたが、ArraysUtilJVM.asList() は Java で実装されていて

    static <T> List<T> asList(T[] array) {
        return Arrays.asList(array);
    }

実装は Java の Arrays.asList() を呼んでいるだけです。

3ファイルにまたがった割には、Arrays.asList() が返す java.util.List なインスタンスを immutable な kotlin.collections.List インターフェース経由で見せているだけという、あまり面白みのないコードでした。

java.util.List と kotlin.collections.List の関係

しかし、よく考えてみると、mutable な java.util.List と、immutable kotlin.collections.List の間にはなんの関係もありません。何事もなくキャストできていますが、この2つは互換性のないインターフェースのはずです。

ここで逆方向の、Kotlin の immutable なリストを Java 側のメソッドに渡すことを試してみましょう。

fun main(args: Array<String>) {
    val xs = listOf("foo", "bar", "baz")
    println(java.util.Collections.disjoint(xs, listOf("foo", "bar")))
}

こんなコードが何事もなかったかのように実行できますが、これも、よく考えると

  • xs は、実行時の実装はさておき、コンパイラから見ると kotlin.collections.List です。
  • java.util.Collections.disjoint は、java.util.List を java.util.Collection 経由で知っていますが、kotlin.collections.List については知りません
  • kotlin.collections.List と java.util.List のあいだには互換性がありません。

だれが java.util.List を kotlin.collections.List に (あるいはその逆を) 変換しているのでしょうか?

Mapped Types

実は、Kotlin コンパイラは Java のいくつかの型を「知って」いて、自動的に Kotlin の型に変換しています。

Mapped types

Kotlin treats some Java types specially. Such types are not loaded from Java "as is", but are mapped to corresponding Kotlin types.

このためのインターフェースは PlatformToKotlinClassMap に、実際の実装は JavaToKotlinClassMap で定義されています。

これにより Kotlin は、mutable/immutable を型として区別できるコレクションライブラリを持ちつつも、それらを明示的な変換なしに Java 側にも渡せるようになっています。

この設計は、例えば標準のコレクションライブラリを自前で丸ごと実装している Scala とは違っています。Kotlin M3 is Out! (2012) では、その判断について

Static JVM languages tend to come up with their own collections libraries, because Java collections do not make use of the cool features these languages have. So we get really cool collections, that are, unfortunately, incompatible, which makes us wrap them or copy when using Java APIs.

Kotlin has many fancy features, but we are eager to interoperate smoothly with Java, and, it turns out, we can have this cake and eat it too.

と説明しています。

まとめと所感

Kotlin のコレクションについて標準ライブラリ、Mapped Types という仕組みとその実装を追っていきました。

筆者は以前に Scala を書いていたせいか、Kotlin の Mapped Types が言語設計者だけに拡張可能で、とくに汎用性のない仕組みとして実装されているのには、だいぶ割り切った設計に感じます。

一方で、拡張可能で汎用性のある仕組みであったはずの implicit conversion を使った scala.collections.JavaConversions

The transparent conversions provided here are considered fragile because they can result in unexpected behavior and performance.

Therefore, this API has been deprecated and JavaConverters should be used instead. JavaConverters provides the same conversions, but through extension methods.

Scala の表舞台から引退しつつあるのをみると、Kotlin のとった判断はこれに対する返答のようにも思えます。


ここまで Soft Skills っぽい話が続いていたので、箸休めにプログラミング言語の話を書いてみました。明日は同期でエンジニアの Tom さんです。