【KotlinConf和訳】Kotlin 1.4以降の展望


以下の和訳/要約です。
What to Expect in Kotlin 1.4 and Beyond - Posted on December 6, 2019 by Svetlana Isakova
Specialist定例会で輪読する目的と自分の理解目的で書き起こしました。

Kotlin 1.4以降に期待されること

KotlinConfでのKeynoteで、Andrey氏がKotlinの進化のために現在注力している分野と、来年中にリリースされるKotlin 1.4の計画について、我々の戦略的な見解を強調しました。

以下のキーノート全体を見てください。
https://www.youtube.com/watch?v=0xKTM0A8gdI

我々のビジョンは、Kotlinがあなたのすべての頑張りへの信頼できる伴侶となり、あなたの仕事のためのデフォルトの言語選択になることです。これを実現するために、すべてのプラットフォームでKotlinを利用可能にします。業界でよく知られている企業の複数の事例研究が、我々がこの方向に向けて順調に前進していることを見せています。

2020年春に登場予定のKotlin 1.4は、Kotlinのエコシステムのために新たな一歩を踏み出すでしょう。

品質重視

何よりも、Kotlin 1.4は品質とパフォーマンスに重点を置きます。Kotlinは、すでに多くのアイデアやアプローチを開拓したモダンな言語です。我々はそれを最新の状態に保ち、常に進化させていきます。しかし現時点では、Kotlinは大きな機能を追加するよりも、全体的なエクスペリエンスを改善することが重要な段階に達していると考えています。このため、Kotlin 1.4ではいくつかの小さな言語変更のみが提供される予定であり、その詳細については後述します。

我々は、KotlinをサポートするIDEのパフォーマンスを向上させることで、すでにいくつかの素晴らしい成果を達成しています。コード補完の速度は、以前のバージョンに比べて大幅に向上しています。

Gradleチームと協力して、Gradleスクリプトの高速化を実現しました。Kotlin 1.3.60では、Android StudioのGradle ImportはKotlin 1.3.10と比べて約2.5倍高速で、メモリ消費量も約75%少なくなっています。

さらに、build.gradle.ktsのロードのCPU使用率はほぼ0です!また、DeveloperモードでKotlin/Nativeをコンパイルする速度は、コードキャッシュを使えば最大2倍になります。

ビルド速度がユーザにとって最大の懸念であることは我々も理解しており、それに対処するためにtoolchainを継続的に改善しています。しかし、インクリメンタルな改善では、製造コードベースの自然な成長に追いつけません。コンパイルを高速化する一方、ユーザがより多くのコードを書くので、全体的なビルド時間は十分に改善されません。本当に高速にするには、コンパイラを再実装する必要があると明らかになりました。

新しいコンパイラ

新しいコンパイラ実装の目標は、非常に高速で、Kotlinがサポートするすべてのプラットフォームを統合し、コンパイラ拡張のためのAPIを提供することです。これは複数年にわたる取り組みのはずですが、少し前から始めているため、新しい実装の一部は1.4年に導入される予定で、移行は非常に緩やかに行われます。既に行われてもいます…例えば、型推論の新しいアルゴリズムを試したことがあるなら、それが新しいコンパイラの一部です。他の部分のアプローチも同じです…つまり、古いバージョンと新しいバージョンの両方が、しばらくの間実験モードで使用可能になるということです。新しいものが安定したら、それがデフォルトになります。

新しいフロントエンドによる高速化

新しいコンパイラに期待される高速化の大部分は、新しいフロントエンド実装によって実現されます。

背景を少し説明すると、コンパイルは、ソースファイルを取得し、それらを段階的に実行可能コードに変換するパイプラインと考えています。このパイプラインの最初の大きなステップは、俗にコンパイラのフロントエンドと呼ばれます。コードの解析、名前の解決、型チェックなどを実行します。エラーの強調表示、定義への移動、およびプロジェクト内でのシンボル使用の検索の際、コンパイラのこの部分がIDE内で機能します。これは今やkotlincが最も多くの時間を費やすステップなので、更に速くしたいのです。

今の実装はまだ完了しておらず、1.4でも完成しません。しかし、時間のかかる作業のほとんどはすでに完了しており、おおよその高速化を測定できます。ベンチマーク(YouTrackとKotlinコンパイラ自体のコンパイル)によると、新しいフロントエンドは既存のものよりも約4.5倍高速です。

バックエンドと拡張性の統合

フロントエンドでコードの分析が完了すると、バックエンドで実行可能ファイルが生成されます。Kotlin/JVM、Kotlin/JS、Kotlin/Nativeという3つのバックエンドがあります。最初の2つは歴史的に独立して書かれており、あまりコードを共有していませんでした。Kotlin/Nativeを開始したとき、Kotlinコードの内部表現(IR)を中心に構築された、新しいインフラストラクチャに基づいていました。そのIRは、仮想マシンのバイトコードに似た機能を提供します。現在、他の2つのバックエンドを同じIRに移行しています。その結果、多くのバックエンドロジックを共有し、統合されたパイプラインを持つことになるでしょう。これにより、ほとんどの機能、最適化、バグ修正をすべてのターゲットに対して1回だけ実行することができます。

我々は徐々に新しいバックエンドに移行します。1.4においてデフォルトで有効になることはなさそうですが、ユーザが明示的に使うかどうかを選択することができます。

共通のバックエンド・インフラストラクチャーは、マルチプラットフォーム・コンパイラー拡張のドアを開きます。パイプラインにプラグインして、すべてのターゲットで自動的に動作するようなカスタム処理や変換を追加することができます。1.4では、そのような拡張機能(APIは後で安定化されるはず)のための公開APIを提供していませんが、すでにコンパイラプラグインを開発しているJetPack Composeを含むパートナーと、緊密に協力しています。

KLibを知る:Kotlinライブラリフォーマット

Kotlinでマルチプラットフォームライブラリを構築し、クライアントがそれに依存できるようにリリースするには、どのプラットフォームでも同じように動作する配布フォーマットが必要です。これがKLibを導入する理由です。KLibは、Kotlinマルチプラットフォーム用のライブラリフォーマットです。KLibファイルには、シリアル化されたIRが含まれています。コードが依存関係として追加する場合があります。コンパイラのバックエンドがこれを取得し、特定のプラットフォーム用の実行可能コードを生成します。JVMのバイトコードのようにKLibを分析し、変換することができます。シリアル化されたIRに対して行われる変換は、KLibが使用されるプラットフォームに影響を与えます。

実際、Kotlin/NativeはKLibsフォーマットを使用してKotlinネイティブライブラリを配布してきましたが、現在は他のバックエンドやマルチプラットフォームライブラリをサポートするようにフォーマットを拡張しています。このフォーマットは1.4では実験的なものになる予定で、将来のバージョンでは安定したABIを提供すべく取り組んでいきます。

その他のマルチプラットフォーム・ニュース

Android StudioでiOSコードを実行する

iOSデバイスやシミュレータ上でKotlinコードを実行、テスト、デバッグできるAndroid Studio用のプラグインを開発中です。プラグインはIntelliJの独自コードを使っているので、非公開のコードになるでしょう。Objective-CやSwiftの言語サポートはなく、AppStoreへのデプロイなど一部の操作には、Xcodeの実行が必要になるかもしれません。が、Kotlinコードを使って行えることは、新しいプラグインがインストールされたAndroid Studioから実行できます。このプラグインのプレビューは、2020年に公開される予定です。

Kotlin/Nativeランタイムの改善

Linux、Windows、macOS、iOSとは別に、Kotlin/NativeはwatchOSとtvOSでも動作するようになったので、事実上どんなデバイスでもKotlinを動かすことができます。また、iOSのKotlinプログラムをさらに高速に動作させるために、Kotlin/Nativeのランタイムパフォーマンスにも取り組んでいます。

コアライブラリ

Kotlinコアライブラリは、すべてのプラットフォームで動作します。これには、すべての基本型とコレクション、kotlinx.coroutineskotlinx.serializationkotlinx.ioを扱うkotlin-stdlibが含まれます。日付のサポートはマルチプラットフォームの世界で本当に必要とされており、我々はそれに取り組んでいます。stdlibには実験的な期間が既に追加されており、DateTimeのサポートも進行中です。

Kotlinライブラリに追加されたもうひとつの重要な機能は、Reactive Streamsのコルーチンベース実装であるFlowです。Flowはデータストリームの処理に優れており、それにはKotlinのパワーを利用しています。人間工学とは別に、Flowはさらなるスピードをもたらします。いくつかのベンチマークでは、既存の人気のあるReactive Streams実装のほぼ2倍速いです。

ライブラリの著者にとって

Kotlinエコシステムにとって、新しいライブラリーの作成は不可欠であるため、我々はライブラリー作成者の体験を改善し続けています。新しいライブラリ・オーサリング・モードは、API安定のために最適な方法のコーディングに役立ちます。また、すべてのプラットフォームでドキュメント生成をサポートするDokka 1.0もリリースする予定です。

マルチプラットフォームWeb

プラットフォーム間でコードを共有することは、モバイルにとっては素晴らしいことですが、Webクライアントにとっても素晴らしいことです。サーバーとモバイルアプリの間で、あらゆるものを共有できるからです。我々はKotlin/JSツールにますます多くの投資をしており、Kotlinコードの変更からブラウザーでの結果表示まで、非常に高速な開発ラウンドトリップを行うことができます。

また、JSの相互運用性も改善され、Kotlinプロジェクトやその他のプロジェクトにNPM依存関係を追加できるようになりました。.d.tsタイプの定義は、Kotlinツールチェーンによって自動的に選択されます。

新しいIRベースのバックエンドでは、バイナリサイズも大幅に改善されます。コンパイルされたJSファイルは、現在のサイズの半分になります。

新しい言語機能

Kotlin 1.4は、いくつかの新しい言語機能を提供します。

KotlinクラスのSAM変換

コミュニティからは、KotlinクラスのSAM変換のサポートを依頼されています(KT-7770)。SAM変換は、1つの抽象メソッドのみを持つインターフェースまたはクラスが、パラメータとして用いられるときに、ラムダを引数として渡す場合に適用されます。次にコンパイラは自動的にラムダを、抽象メンバ関数を実装するクラスのインスタンスに変換します。

SAM変換は現在、Javaインターフェースと抽象クラスに対してのみ機能します。この設計の背景にある最初のアイデアは、このようなユースケースに対して関数型を明示的に使用することでした。しかし、関数型とタイプエイリアスはすべてのユースケースを網羅しているわけではなく、多くの場合、Javaでインターフェースを保持しなければならず、そのためのSAM変換を得ることしかできませんでした。

Javaとは異なり、Kotlinは1つの抽象メソッドですべてのインターフェースをSAM変換することはできません。SAM変換に適用可能なインターフェースを作成する意図は、明確であるべきであると考えます。したがって、SAMインターフェースを定義するには、funキーワードでインターフェースをマークして、汎用関数インターフェースとして使用できることを強調する必要があります。

fun interface Action {
    fun run()
}

fun runAction(a: Action) = a.run()

fun main() {
    runAction {
        println("Hello, KotlinConf!")
    }
}

fun interfaceの代わりにラムダを渡すことは、新しい型推論アルゴリズムでのみサポートされることに注意してください。

名前付き引数と位置指定引数の混在

Kotlinは、すべての定位置引数の後に名前付き引数を置く場合を除き、明示的な名前を持つ引数(named)と名前のない通常の引数(Positional)を混在させることを禁止しています。しかし、すべての引数が正しい位置にあり、途中で1つの引数の名前を指定する必要がある場合、これが非常に面倒に感じます。Kotlin 1.4はこの問題を解決し、以下のようなコードを書けるようになります。

fun f(a: Int, b: Int, c: Int) {}

fun main() {
    f(1, b = 2, 3)
}

最適化された委任プロパティ

lazyプロパティーや、その他の委任プロパティーをコンパイルする基本的な方法を改善します。

通常、委任されたプロパティーは、対応するKPropertyというリフレクションオブジェクトにアクセスできます。たとえば、Delegates.observableを使用する場合、変更されたプロパティに関する情報を表示できます。

import kotlin.properties.Delegates

class MyClass {
    var myProp: String by Delegates.observable("<no name>") {
        kProperty, oldValue, newValue ->
        println("${kProperty.name}: $oldValue -> $newValue")
    }
}

fun main() {
    val user = MyClass()
    user.myProp = "first"
    user.myProp = "second"
}

これを可能にするために、Kotlinコンパイラは追加の構文メンバプロパティを生成します。このプロパティは、クラス内で使用される委任プロパティを表すすべてのKPropertyオブジェクトを格納する配列です。

>>> javap MyClass

public final class MyClass {
    static final kotlin.reflect.KProperty[] $$delegatedProperties;
    ...
}

ただし、KPropertyを使用しない委任プロパティもあります。それらについては、$$delegatedProperties内でのオブジェクトの生成は最適ではありません。Kotlin 1.4リリースは、このようなケースを最適化します。委任されたプロパティ演算子がinlineで、KPropertyパラメーターが使用されていない場合、対応するリフレクションオブジェクトは生成されません。

最も顕著な例は、lazyプロパティです。lazyプロパティーに対するgetValueの実装はinlineであり、KPropertyパラメーターは使われません。

inline operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = value

Kotlin 1.4以降では、lazyプロパティーを定義しても、対応するKPropertyインスタンスは生成されません。クラスで使用する委任プロパティがlazyプロパティ(最適化に適合する他の特性)のみの場合、クラスの$$delegatedProperties配列全体が生成されません。

class MyOtherClass {
    val lazyProp by lazy { 42 }
}

>>> javap MyOtherClass
public final class MyOtherClass {
    // no longer generated:
    static final kotlin.reflect.KProperty[] $$delegatedProperties; 
    ...
}

末尾のコンマ

このちょっとした構文変更が、信じられないほど便利なことがわかりました。パラメータリストの最後のパラメータの後にコンマを追加できます。それから、行を入れ替えたり、新しいパラメータを追加したりできます。足りないカンマを追加または削除する必要はありません。

その他の注目すべき変更点

Kotlin 1.3.40で導入された便利な関数のtypeof型が安定し、すべてのプラットフォームでサポートされるようになります。

1.3.60リリースのブログ投稿ですでに説明されているように、when内でのbreakcontinueを有効にします。

ありがとうございます!

Kotlin EAPと実験的な機能を試し、フィードバックをくれた皆様に本当に感謝しています。我々は皆様と一緒にKotlin言語を開発し、皆さんの貴重なインプットに基づいて多くの設計の決定を行っています。この迅速で効果的なフィードバックループをコミュニティと共有することは、Kotlinがベストな状態になるためには非常に重要です。

Kotlinを使って、素晴らしいものをたくさん作ってくれたコミュニティのメンバー全員に感謝しています。これからもKotlinと一緒に続けましょう!

ちなみに、IntelliJ IDEAとAndroid Studio内のKotlinプラグインは、その機能の利用に関する匿名の統計を収集します。何がうまくいっているのか、何が問題を引き起こしているのか、何を改善すべきなのかを理解するのに役立つので、これらの統計に同意するようにお願いします。