Kotlinのstdlibのgeneratedについて

  • 5
    いいね
  • 0
    コメント

stdlibと拡張関数とコレクション

Kotlinのstdlibはほぼ拡張関数の集まりなのですが、Javaにはプリミティブ型があり、それらは型パラメータとして扱えないため、コレクションに対して拡張関数を書くには以下のように型パラメータ、byte、short、int、long、float、double、boolean、charに対して似たようなコードを繰り返し書かなければなりません。

/**
 * Returns `true` if the array is empty.
 */
@kotlin.internal.InlineOnly
public inline fun <T> Array<out T>.isEmpty(): Boolean {
    return size == 0
}

/**
 * Returns `true` if the array is empty.
 */
@kotlin.internal.InlineOnly
public inline fun ByteArray.isEmpty(): Boolean {
    return size == 0
}

/**
 * Returns `true` if the array is empty.
 */
@kotlin.internal.InlineOnly
public inline fun ShortArray.isEmpty(): Boolean {
    return size == 0
}

/**
 * Returns `true` if the array is empty.
 */
@kotlin.internal.InlineOnly
public inline fun IntArray.isEmpty(): Boolean {
    return size == 0
}

/**
 * Returns `true` if the array is empty.
 */
@kotlin.internal.InlineOnly
public inline fun LongArray.isEmpty(): Boolean {
    return size == 0
}

/**
 * Returns `true` if the array is empty.
 */
@kotlin.internal.InlineOnly
public inline fun FloatArray.isEmpty(): Boolean {
    return size == 0
}

/**
 * Returns `true` if the array is empty.
 */
@kotlin.internal.InlineOnly
public inline fun DoubleArray.isEmpty(): Boolean {
    return size == 0
}

/**
 * Returns `true` if the array is empty.
 */
@kotlin.internal.InlineOnly
public inline fun BooleanArray.isEmpty(): Boolean {
    return size == 0
}

/**
 * Returns `true` if the array is empty.
 */
@kotlin.internal.InlineOnly
public inline fun CharArray.isEmpty(): Boolean {
    return size == 0
}

stdlibのコード生成

そこで、Kotlinのstdlibではコレクションに対する拡張関数をテンプレートからコード生成しており、生成されたコードは libraries/stdlib/src/genereted/ に置かれます。

Screen Shot 2016-08-27 at 12.32.30 PM.png

例えば上の isEmpty のテンプレートは libraries/tools/kotlin-stdlib-gen/src/templates/Arrays.kt に以下のように定義されています。

templates add f("isEmpty()") {
    inline(Inline.Only)
    only(ArraysOfObjects, ArraysOfPrimitives)
    doc { "Returns `true` if the array is empty." }
    returns("Boolean")
    body {
        "return size == 0"
    }
}

少しトリッキーに見えますが f の定義を見てみると

fun f(signature: String, init: GenericFunction.() -> Unit) = GenericFunction(signature).apply(init)

となっており、ブロックのレシーバーが GenericFunction で、そのクラスがメソッドのビルダーになっていることが分かります。

テンプレートからコードを生成するには以下のコマンドを実行します。

$ cd libraries/tools/kotlin-stdlib-gen
$ mvn compile exec:java

これで libraries/stdlib/src/generated/Arrays.ktisEmpty が生成されます。

stdlibのコレクションに拡張関数を追加する

実際に peek という、デバッグのためにリストの中身を覗き見るためのメソッドを追加してみます。

listOf(1, 2, 3)
        .map { ... }
        .peek { println(it) } //=> 1, 2, 3
        .map { ... }

ビルダーのメソッドの一部を紹介します。

name what
inline 生成されるメソッドをインラインにするかを指定します
typeParam 型パラメータを指定します
only どのコレクション(Familyと呼ばれています)に対して拡張関数を定義するかを指定します
returns 返り値の型を指定します
body メソッドの中身はここに書きます

雑な peek の実装は以下のようになります。

templates add f("peek(action: (T) -> Unit)") {
    inline(true)
    only(Lists)
    typeParam("T")
    returns("List<T>")
    body {
        """
            for (item in this)
                action.invoke(item)
            return this
        """
    }
}

テンプレートの定義は独自記法で少しクセがあるので先に generated の方に実装したいメソッドを書いてみて、動いたらテンプレートに移すのが良いと思われます。

書けたらビルドして実行してみます。

$ mvn compile exec:java
$ ant runtime
$ dist/kotlinc/bin/kotlinc
Type :help for help, :quit for quit
>>> listOf(1,2,3).peek { println(it) }
1
2
3
[1, 2, 3]

Familyについて

Family はどのクラスに対して生成するのか対応になっています。

templatesonlyFamily 指定します。デフォルトは全てのFamilyに対して生成するようになっており、対応は以下の通りです。

Family packageName SourceFile
Iterables "kotlin.collections" Collections
Collections "kotlin.collections" Collections
Lists "kotlin.collections" Collections
Sets "kotlin.collections" Sets
Maps "kotlin.collections" Maps
InvariantArraysOfObjects "kotlin.collections" Arrays
ArraysOfObjects "kotlin.collections" Arrays
ArraysOfPrimitives "kotlin.collections" Arrays
Sequences "kotlin.sequences" Sequences
CharSequences "kotlin.text" Strings
Strings "kotlin.text" Strings
Ranges "kotlin.ranges" Ranges
RangesOfPrimitives "kotlin.ranges" Ranges
ProgressionsOfPrimitives "kotlin.ranges" Ranges
Generic "" Misc
Primitives "" Misc

peek の例では only に Lists を指定したので libraries/stdlib/src/generated/_Collections.kt にメソッドが生成された、という感じです。