Kotlin
KotlinDay 4

Kotlinのstdlibのコードのうちいくつかは自動生成されている

この投稿の内容および参照先のコードは、Kotlin 1.3.20です。


よく使うあの関数、自動生成されたコードで記述されている

Kotlinの各種コレクションの拡張関数であるmapfilterなどはstdlib内に定義されています。

さてこの拡張関数が定義されているコードの先頭部分には次のようなことが記載されています。

//

// NOTE: THIS FILE IS AUTO-GENERATED by the GenerateStandardLib.kt
// See: https://github.com/JetBrains/kotlin/tree/master/libraries/stdlib
//

どうやらmapfilterが定義されているコードは、自動生成されているようです。

上記に記述のある通り、libraries/stdlibを参照してみましょう。ここのReadMeは次のようになっています。

スクリーンショット 2018-11-28 17.00.53.png

やはり自動生成が行われているようです。こちらの一文に注目してください。


This then runs the script which generates a significant part of stdlib sources from the templates authored with a special kotlin based DSL.


自動生成のコードはKotlinのDSLとなっています。この投稿の残りでは、そのDSLを覗いてみましょう。


stdlibの自動生成のDSLをのぞいてみよう

stdlibのコードの自動生成を行なうDSLは、以下の場所にあります。

https://github.com/JetBrains/kotlin/tree/1.3.20/libraries/tools/kotlin-stdlib-gen


エントリーポイントは?

まずはbuild.gradleをみてみましょう。

https://github.com/JetBrains/kotlin/blob/1.3.20/libraries/tools/kotlin-stdlib-gen/build.gradle

ここの内容から、エントリーポイントはgenerators.GenerateStandardLibKtであることがわかりました。

次はこのgenerators.GenerateStandardLibKtをみてみましょう。

templatesパッケージには、次のようなTemplateGroupのサブクラスが定義されており、それを列挙しています。

    val templateGroups = sequenceOf<TemplateGroup>(

Elements,
Filtering,
Ordering,
ArrayOps,
Snapshots,
Mapping,
SetOps,
Aggregates,
Guards,
Generators,
StringJoinOps,
SequenceOps,
RangeOps,
Numeric,
ComparableOps
)

このFilteringやMapping、SnapshotsなどはDSLを活用して記述されています。

下記のコードで、プラットフォーム別の目的のディレクトリにアウトプットします。

    templateGroups.groupByFileAndWrite { (platform, source) ->

// File("build/out/$platform/$source.kt")
when (platform) {
Platform.Common -> commonDir.resolve("_${source.name.capitalize()}.kt")
Platform.JVM -> jvmDir.resolve("_${source.name.capitalize()}Jvm.kt")
Platform.JS -> jsDir.resolve("_${source.name.capitalize()}Js.kt")
Platform.Native -> error("Native is unsupported yet")
}
}


DSLで記述された部分は?

先ほども出てきた次のlibraries/tools/kotlin-stdlib-gen/src/template下にあるクラスは、DSLを用いて記述されています。


  • Elements

  • Filtering

  • Ordering

  • ArrayOps

  • Snapshots

  • Mapping

  • SetOps

  • Aggregates

  • Guards

  • Generators

  • StringJoinOps

  • SequenceOps

  • RangeOps

  • Numeric

  • ComparableOps

それぞれの内容は、関数の系統ごとクラスごとにまとめられています。

たとえば、


  • Mappingは、mapやmapNotNullなどの射影とそれに近い処理を行う関数

  • Filteringは、filterやtakeWhileなどの選択とそれに近い処理を行う関数

  • SetOps(ファイル名はSets、クラス名はSetOps)は、unionやintersectなどの集合演算を行う関数

がまとめられています。

各種コレクション型のfilter関数を生成する部分をみてみましょう。

    val f_filter = fn("filter(predicate: (T) -> Boolean)") {

includeDefault()
include(CharSequences, Strings)
} builder {
inline()

doc { "Returns a ${f.mapResult} containing only ${f.element.pluralize()} matching the given [predicate]." }
returns("List<T>")
body {
"""
return filterTo(ArrayList<T>(), predicate)
"""

}

specialFor(Strings, CharSequences) {
returns("SELF")
doc { "Returns a ${f.collection} containing only those characters from the original ${f.collection} that match the given [predicate]." }
body { """return filterTo(StringBuilder(), predicate)${toResult(f)}""" }
}

specialFor(Sequences) {
inline(Inline.No)
returns("Sequence<T>")
body { """return FilteringSequence(this, true, predicate)""" }
}
}

このようにKotlinのDSLを活用して記述されています。

上記のコード中のdocはコメント部を、bodyはメソッドの実装の生成を担っています。

次のコメントを担っている部分に注目してください。Kotlinの言語機能を活用していますね。

doc { "Returns a ${f.mapResult} containing only ${f.element.pluralize()} matching the given [predicate]." }

また、Sequenceに対してfilterメソッドは、inline化を行わないなどの特殊化をする必要があります。そこで上記のコードのようにspecialForを使って特殊化をしています。

specialFor(Sequences) {

inline(Inline.No)
returns("Sequence<T>")
body { """return FilteringSequence(this, true, predicate)""" }
}

これらのDSLで記述したコードは、どのような効果があるか簡単に想像できるのではないでしょうか?


DSLの定義は?

KotlinのDSLについては以下の公式ドキュメントを参照してください。

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-dsl-marker/index.html

https://kotlinlang.org/docs/reference/type-safe-builders.html

https://kotlinlang.org/docs/reference/type-safe-builders.html#scope-control-dslmarker-since-11

特に、Kotlin 1.1から利用可能な@DslMakerに注目してください。

stdlibのコードジェネレーターDSLの実装は、libraries/tools/kotlin-stdlib-gen/src/templates/dslにあるコード群です。

@DskMakerを活用しているのが、TemplateDslアノテーションです。

それを活用しているのが次のクラス群です。


まとめ

Kotlinでコーディングをしているときによく使うmapやfilterという関数のコードは、実は自動生成されています。

そしてその自動生成のコードはKotlinの言語機能を活用したDSLで実装されています。

もしあなたが、KotlinでDSLを作ることになったときにもしかしたら参考になるかもしれません。ぜひ覗いてみてください。


関連スライド