Kotlin 1.4-M1リリースの紹介
こちらは2020年3月23日にリリースされたKotlin 1.4-M1 Releasedの日本語訳です。
新しいメジャーリリースの最初のプレビューバージョン「Kotlin 1.4-M1」が利用可能です。
Kotlin 1.4に期待される内容については、数か月前に発表ましたが、 リリース時期が近付くにあたり、プレビュー版を公開することになりました。一部の新しい機能を皆さん自身でお試しいただけます。
この記事では、1.4-M1に追加された新しい機能と主な改善内容を紹介します。
- さらに強化された新しい型推論アルゴリズムは、デフォルトで有効化されています。
- コントラクトをfinalメンバ関数で使用できるようになりました。
- Kotlin/JVMコンパイラで、Java 8以降のターゲットの型の注釈をバイトコードに生成できるようになりました。
- Kotlin/JSのバックエンドが新しくなり、アーティファクトの生成が大きく改善されました。
- 標準ライブラリが大幅に変更されました。廃止サイクルは完了し、ほかの一部が廃止予定になりました。
完全な変更リストは変更ログで確認できます。 これまで同様、社外貢献者の皆様からのご協力に深く感謝いたします。
プレビュー版をぜひお試しください。課題トラッカーに提出される皆様からのフィードバックを楽しみにお待ちしています。
さらにパワフルになった型推論アルゴリズム
Kotlin 1.4には、より強力な型推論アルゴリズムが採用されました。 この新しいアルゴリズムをKotlin 1.3で使用するには、コンパイラ・オプションを指定する必要がありましたが、デフォルトで使用できるように変更されています。 新しいアルゴリズムで修正されたすべての課題については、YouTrackをご覧ください。 このブログ記事では、最も大きな変更点について紹介しています。
Kotlin関数とインターフェースのSAM変換
SAM関数を使うと、1つの「Single Abstract Method」を使ったインターフェースが期待される場合に、ラムダ式を渡すことができます。 以前は、KotlinでJavaメソッドとJavaインターフェースを使う場合には、SAM変換を適用することしかできませんでしたが、現在は、Kotlin関数とインターフェースも合わせて使用できます。
KotlinはKotlinインターフェースのSAM変換をサポートできるようになりました。 ただし、Javaとは異なり、関数型インターフェースを明示的にマークする必要があります。 「fun
」キーワードを使ってインターフェイスをマークすると、このようなインターフェースがパラメータとして期待される場合に、ラムダ式を引数として渡せるようになります。
これに関する詳しい情報は、以前のブログ記事でお読みください。
Kotlinは当初からJavaインターフェースのSAM変換をサポートしてきましたが、サポートされていないケースが1つだけあり、それにより既存のJavaライブラリがかかわる場合に面倒な場合がありました。 2つのSAMインターフェースをパラメータとして取るJavaメソッドを呼び出した場合、両方の引数がラムダ式か普通のオブジェクトのいずれかでなければなりません。 ラムダ式ともう1つのオブジェクトが混在する1つの引数を1つのオブジェクトとして渡すことはできなかったのです。 新しいアルゴリズムではこの課題が修正され、どのような状況でもSAMインターフェースの代わりにラムダ式を渡すことができるようになりました。これが当然の動作です。
より多くのユースケースで自動的に型を推論
新しいインターフェースアルゴリズムでは、以前の推論では明示的に指定する必要のあった数多くのケースでも型を推論できるようになりました。 たとえば、次の例では、ラムダ式のパラメータ it
の型は、正しく String?
に推論されています。
Kotlin 1.3では、明示的なラムダ式のパラメータを導入するか、 to
を明示的な汎用引数を使った Pair
コンストラクタに変えなければ動作しませんでした。
ラムダ式の最後の式のスマートキャスト
Kotlin 1.3では、ラムダ式内の最後の式は、期待される型を指定しない限り、スマート・キャストではありませんでした。 そのため、次の例では、Kotlin 1.3は String?
を result
変数の型として推論しています。
Kotlin 1.4では、新しいインターフェースアルゴリズムの導入により、ラムダ式の最後の式がスマートキャストされます。ラムダ型の推論には、この新しい、より正確な型が使用されるため、 result
変数の型は String
になります。
Kotlin 1.3でこういったケースを動作させるには、明示的なキャスト(!!
または as String
のような型キャスト)を追加しなければならないことがよくありましたが、これらは不要になりました。
呼び出し可能参照のスマートキャスト
Kotlin 1.3では、スマートキャスト型のメンバ参照にアクセスすることはできませんでした。 以下を行えるようになりました:
animal変数が特定の型 Cat
と Dog
にスマートキャストされた後に、別のメンバ参照 animal::meow
と animal::woof
を使用することができます。 型チェックを実施した後、サブタイプに対応するメンバ参照にアクセスできます。
呼び出し可能参照の推論の改善
関数への呼び出し可能参照をデフォルトの引数値を使ってより便利に使用できるようになりました。 たとえば、次の foo
関数への呼び出し可能参照は、1つの Int
引数を取るか引数を何も取らないの両方に解釈できます。
デリゲート型プロパティの推論を改善
by
キーワードの後に続くデリゲート式を解析する場合、デリゲート型プロパティの型は考慮されていませんでした。 たとえば、以前は次のコードはコンパイルしませんでしたが、コンパイラは old
と new
パラメータの型を String?
として正しく推論できるようになりました。
言語に関する変更
言語に関する変更内容については、そのほとんどが以前のブログ記事で説明されています。
この記事では、コントラクトに関する小さな改善点を一部説明します。
コントラクトのサポート
カスタムコントラクトを定義する構文は実験段階にありますが、コントラクトが役に立つような新しいケースを2つサポートしました。
具体化(reified)されたジェネリック型パラメータを使ってコントラクトを定義できるようになりました。 たとえば、assertIsInstance
関数で次のコントラクトを実装できます。
型パラメータ T
が具体化されているため、関数本体で型をチェックできます。 これは、コントラクト内でも可能になりました。 アサーションメッセージを使った似たような関数は、後日、kotlin.test
ライブラリに追加される予定です。
また、final
メンバでカスタムコントラクトを定義できるようにもなりました。 以前は、階層の一部のメンバにコントラクトを定義することは、対応するコントラクトの階層を暗に示すことになるため、メンバ関数のコントラクトを定義することは完全に禁じられていました。現在でも、設計と議論が続いています。 ただし、メンバ関数が final
であり、ほかの関数をオーバーライドしないのであれば、それにコントラクトを定義しても安全といえます。
標準ライブラリの変更
非推奨の実験的(deprecated experimental)コルーチンの除外
kotlin.coroutines.experimental
APIは、1.3.0における kotlin.coroutines
の導入により非推奨となりました 。 1.4-M1では、kotlin.coroutines.experimental
は標準ライブラリから削除され、deprecateサイクルが完了することになっています。 JVMで使用しているユーザーを考慮し、互換性アーティファクト kotlin-coroutines-experimental-compat.jar
には、すべての実験的コルーチンAPIを提供しています。 これをMavenに公開し、標準ライブラリとは別にKotlinディストリビューションに含める意向です。 現時点では、1.4-M1アーティファクトとともに、バイナリリポジトリに公開されています。
非推奨のmod演算子の削除
別の非推奨関数として数値型に対する mod
演算子があります。これは、除算の後に剰余を計算する演算子です。 Kotlin 1.1において rem()
関数に置き換えられた演算子ですが、 今回、標準ライブラリから完全に削除されることになりました。
floating型からByte型およびShort型への変換の廃止
標準ライブラリには、浮動小数点数を整数型に変換する toInt()
、toShort()
、toByte()
という関数があります。 値の範囲が狭く、可変サイズがより小さいことから、浮動小数点数を Short
型と Byte
型に変換することで、予期しない結果が生まれてしまいます。 こういった問題を回避するために、1.4-M1において、Double
型と Float
型での toShort()
関数と toByte()
関数を非推奨としました。 浮動小数点数を Byte
型や Short
型に変換する必要がある場合は、2段階変換により、まず Int
型に変換してから目標の型に変換するようにしてください。
共通リフレクションAPI
共通リフレクションAPIを改定しました。 3つの全ターゲットプラットフォーム(JVM、JS、ネイティブ)で使用できるメンバーのみが含まれるようになったため、すべてのプラットフォームで同じコードを動作させることができます。
use() と時間測定関数の新しいコントラクト
標準ライブラリにおけるコントラクトの使用方法が拡大されます。 1.4-M1では、use()
関数と、時間測定関数の measureTimeMillis()
と measureNanoTime()
で、1つのコードブロック実行を宣言するコントラクトを追加しました。
KotlinリフレクションのProGuard構成
1.4-M1よりKotlinリフレクションに対し、ProGuard/R8構成を kotlin-reflect.jar
に組み込みました。 これにより、R8またはProGuardを使用するほとんどのAndroidプロジェクトで、ほかに構成マジックを使うことなく、kotlin-reflectを動作させられるようになります。 kotlin-reflect内部にProGuradルールをコピー・ペーストする必要はなくなりますが、 リフレクションを適用するすべてのAPIを明示的にリストすることは必要です。
Kotlin/JVM
Kotlinは、バージョン1.3.70より、ランタイム時に使用できるように型の注釈をJVMバイトコードに生成できるようになりました(ターゲットバージョン1.8以降)。 この機能は一部の既存のJavaライブラリをはるかに使いやすくし、新しいライブラリの作成者をより強力に支援するため、コミュニティから長らく要望があがっていました。
以下の例では、String
型の @Foo
注釈をバイトコードに出力してライブラリコードで使用できます。
バイトコードで型の注釈を出力する方法については、Kotlin 1.3.70リリースに関するブログ記事の該当セクションを参照してください。
Kotlin/JS
Kotlin/JSについては、Gradle DSLにいくつかの変更があります。新しいIRコンパイラ・バックエンドを導入した最初のバージョンであり、最適化と新しい機能が有効になります。
Gradle DSLの変更点
kotlin.js
および multiplatform
のGradleプラグインに、新たに重要な設定が導入されました。 build.gradle.kts
ファイルのターゲット・ブロック内でproduceExecutable()
設定を使用できるようになり、ビルド中に .js
アーティファクトを生成する場合は、これが必須となりました。
Kotlin/JSライブラリを記述する場合は、produceExecutable()
を省略できます。 新しいIRコンパイラバックエンドを使用する際(詳細は以下で説明)、この設定を省略すると、実行可能なJSファイルは生成されません(そのため、ビルドプロセスの実行が加速化します)。 klib
ファイルは build/libs
フォルダに生成されるため、ほかのKotlin/JSプロジェクトから使用するか、同じプロジェクトで依存関係として使用できます。 produceExecutable()
を明示的に指定しない場合、この動作がデフォルトで発生します。
produceExecutable()
を使用すると、独自のエントリポイント付きかJavaScriptライブラリとしてJavaScriptエコシステムから実行可能なコードが生成されます。 これにより、実際のJavaScriptファイルが生成され、それをノード・インタプリタで実行したり、HTMLページに埋め込んでブラウザで実行したり、JavaScriptプロジェクトの依存関係として使用したりすることができるようになります。
新しいIRコンパイラバックエンドをターゲットする場合(詳細は以下で説明)、produceExecutable()
は必ず1つのスタンドアロン .js
ファイルをターゲットごとに生成します。 現在、重複解除や、複数の生成アーティファクト間でコードを分割することはサポートされていません。 produceExecutable()
のこの動作は、今後公開されるマイルストーンで変更される予定です。 このオプションの名前についても、今後変更されることがあります。
新しいバックエンド
Kotlin 1.4-M1で、Kotlin/JSターゲットの新しいIRコンパイラバックエンドが初めて導入されました。 このバックエンドは、最適化の大幅な改善の基盤であり、Kotlin/JSがJavaScriptやTypeScriptと対話する方法を一部変更するな要因となっています。 以下にハイライトする機能は、すべて新しいIRコンパイラバックエンドを対象としています。 デフォルトではまだ有効化されていませんが、プロジェクトで試用し、新しいバックエンドに合わせてライブラリの準備を始めることをお勧めします。その上で、皆さんからのフィードバックをお待ちしています。問題が発生した場合には、ぜひ、その課題をログに記録してください。
新しいバックエンドの使用
新しいバックエンドを使用し始めるには、gradle.properties
ファイルに次のフラグを設定してください。
IRコンパイラバックエンドとデフォルトバックエンド用にライブラリを生成する場合は、このフラグを both
に設定することができます。 このフラグの実際の作用については、このブログの「“Both-mode”」セクションで説明しています。 新しいコンパイラバックエンドとデフォルトのコンパイラバックエンドにバイナリ互換性がないため、このフラグが必要となっています。
バイナリの非互換性
新しいIRコンパイラバックエンドの大きな変更点は、デフォルトバックエンドとのバイナリ互換性がないところにあります。 Kotlin/JSの2つのバックエンドでそのような互換性がないため、新しいIRコンパイラバックエンドで作成されたライブラリをデフォルトバックエンドで使用することができません。またその逆も同様です。
プロジェクトにIRコンパイラバックエンドを使用する場合は、すべてのKotlin依存関係を、新しいバックエンドをサポートするバージョンに更新する必要があります。 Kotlin/JSをターゲットとしてKotlin 1.4-M1向けにJetBrainsが公開したライブラリには、すでに、新しいIRコンパイラバックエンドでの使用に必要なすべてのアーティファクトが含まれています。 このライブラリを使用する場合、Gradleによって正しいアーティファクトが自動的に選択されるため、IR固有の管理を指定する必要はありません。 kotlin-wrappers
などの一部のライブラリはデフォルトバックエンド固有の特性に依存しているため、新しいIRコンパイラバックエンドでは問題となる場合がありますので注意してください。 この課題の存在はすでに報告済みであり、今後の機能改善に努めています。
現在のコンパイラバックエンドだけでなく、新しいIRコンパイラバックエンドとの互換性の提供を考えているライブラリの作成者は、このブログ記事の「Both-mode」も合わせて参照してください。
次のセクションでは、新しいコンパイラのメリットと変更点をより詳しく説明します。
最適化されたDCE(Dead Code Elimination:デッドコードの削除)
新しいIRコンパイラバックエンドは、デフォルトバックエンドに比べてより積極的な最適化を実行できます。 生成されたコードはStatic Analyzerとよりうまく連携するだけでなく、それをGoogleのClosure Compilerを通じて新しいIRコンパイラバックエンドから実行したり、Advancedモードの最適化機能を使用したりすることもできます(ただし、最適化機能の使用は、Kotlin/JS Gradleプラグインでサポートされた機能ではありません)。
一番わかりやすい変更点は、生成されたアーティファクトのコードサイズにあります。 デッドコードを削除する方法が改善され、アーティファクトを大幅に縮小できるようになったところです。 たとえば、この改善により、“Hello, World!” Kotlin/JSプログラムは1.7 KiB未満に縮小されます。 kotlinx.coroutines
を使用したこのサンプルプロジェクトのようにより複雑な(デモ)プロジェクトの場合は、次に示されるように数字も大幅に変化します。
デフォルトバックエンド | IRバックエンド | |
---|---|---|
コンパイル後 | 3.9 MiB | 1.1 MiB |
JS DCE後 | 713 KiB | 430 KiB |
バンドル後 | 329 KiB | 184 KiB |
ZIP後 | 74 KiB | 40 KiB |
いまいち納得がいかない方は、自分で試してみるとよいでしょう。 Kotlin 1.4-M1では、両方のバックエンドでDCEとバンドル化が有効化されています!
JavaScriptへの宣言のエクスポート
IRコンパイラバックエンドを使うと、publicにマークされた宣言は自動的にエクスポートされなくなりました。 これは、IRコンパイラの closed-world モデルが、エクスポートされた宣言が具体的に注釈付けされているとみなすためです。具体的な注釈は、上に示したような最適化を行うための一要因です。
トップレベルの宣言をJavaScriptやTypeScriptで利用できるようにするには、@JsExport
注釈を使用するようにしてください。 次の例では、KotlinGreeter
(とそのメソッド)と farewell()
をJavaScriptで使用できるようにすると同時に、 secretGreeting()
をKotlin限定にしています。
プレビュー: TypeScriptの定義
新しいKotlin/JS IRコンパイラのもう1つの機能として公開するのを楽しみにしているのは、KotlinコードからTypeScript定義を生成する機能です。 これらの定義は、ハイブリッドアプリを制作する際にJavaScriptツールとIDEで使用できるもので、自動補完機能の提供、Static Analyzerのサポート、JSおよびTSプロジェクトへのKotlinコード組み込みの簡単化を実現します。
produceExecutable()
を使用するように構成されたプロジェクトにおいて @JsExport
でマークされたトップレベルの宣言については(上記参照)、TypeScript定義を含む .d.ts
ファイルが生成されます。 上のスニペットでは、次のようになります。
Kotlin 1.4-M1では、これれらの宣言は、対応するwebpackされていないJavaScriptコードとともに、build/js/packages/<package_name>/kotlin
にあります。 ただし、これはプレビュー機能であるため、今のところデフォルトでは distributions
フォルダに追加されていません。 この動作は、今後変更される予定です。
Both-mode
新しいIRコンパイラ・バックエンドに移行するライブラリ管理者の作業を簡略化できるように、gradle.properties
内に kotlin.js.compiler
フラグの設定が追加されています。
both
モードが使用されている場合、ソースからライブラリをビルドする際にIRコンパイラバックエンドとデフォルトバックエンドの両方が使用されます(これが名前の由来です)。 つまり、生成されるのは、Kotlin IRを使う klib
ファイルとデフォルトコンパイラ用の js
ファイルの2つです。 同一のMavenコーディネートで公開されると、Gradleはユースケースに応じて適切なアーティファクトを自動的に選択し、以前のコンパイラであれば js
、新しいコンパイラであれば klib
を選択するため、 Kotlin 1.4-M1にアップグレード済みのプロジェクトと、いずれかのコンパイラバックエンドを使用するプロジェクトに対し、新しいIRコンパイラバックエンドを使ってライブラリをコンパイルして公開することができます。 プロジェクトが1.4-M1に更新されているのであれば、デフォルトバックエンドを使用しているユーザーの開発体験を中断しなくてよいので役に立ちます。
依存関係および現在のプロジェクトが both
モードを使用している場合は、IDEがライブラリの参照を適切に解決できなくなる課題が現在も残っています。 この問題は報告済みで、近日修正される予定です。
Kotlin/Native
Objective-Cのジェネリクスをデフォルトサポート
以前のバージョンのKotlinで、Objective-Cの相互運用性にジェネリクスの実験的サポートが導入されました。 Kotlinコードからジェネリクスを使ってフレームワークヘッダーを生成するには、-Xobjc-generics
コンパイラオプションを使用する必要がありました。 1.4-M1では、これがデフォルトの動作です。 一部のケースでは、これによってKotlinフレームワークを呼び出す既存のObjective-CまたはSwiftコードが壊れてしまうことがありますので、 ジェネリクスを使用せずにフレームワーク・ヘッダーを記述する場合は、-Xno-objc-generics
コンパイラ・オプションを追加してください。
ドキュメントに記載されているすべての指定子と制限は、現在も適用されます。
Objective-C/Swift相互運用性における例外処理の変更
1.4では、例外が解釈される方法に関し、Kotlinから生成されるSwift APIがわずかに変更される予定です。 KotlinとSwiftでは、エラーの処理の仕方が根本的に異なります。 すべてのKotlin例外はチェック対象外であり、Swiftはエラーのみをチェックします。 そのため、期待される例外をSwiftコードに知らせるには、Kotlin関数を @Throws
注釈でマークし、潜在的な例外クラスのリストを指定しなければなりません。
SwiftまたはObjective-Cフレームワークにコンパイルする際、@Throws
注釈を持つ関数やそれを継承する関数は、Objective-Cでは NSError*
が生成するメソッドとして、Swiftでは throws
メソッドとして表されます。
以前は、RuntimeException
と Error
を除くすべての例外は、NSError
として伝搬されていました。 1.4-M1ではこの動作が変更され、 NSError
は、例外が @Throws
注釈のパラメータとして指定されたクラス(またはそのサブクラス)のインスタンスである場合にのみ投げられます。 Swift/Objective-Cに届くその他のKotlin例外は、未処理となり、プログラムが終了する原因となります。
パフォーマンスの改善
Kotlin/Nativeコンパイルと実行のパフォーマンスを全体的に改善できるように、引き続き努めています。 1.4-M1では、一部のベンチマークに対し最大2倍の速度で動作する新しいオブジェクトアロケータを提供しています。 現在のところ、新しいアロケータは実験的であり、デフォルトでは使用されていません。-Xallocator=mimalloc
コンパイラオプションを使用すると、そのアロケータに切り替えることができます。
互換性
一部のコーナーケースにおいて、Kotlin 1.4には1.3との下位互換性はありません。 Kotlin言語委員会はそういったケースを注意深くレビューし、「互換性ガイド」(これに似た様式)として掲載する予定です。 現時点では、YouTrackでこのリストをご覧ください。
オーバーロードの解決ルールがわずかに変更される可能性があります。 同じ名前で異なるシグネチャーを持つ関数が複数ある場合、Kotlin 1.4に読み込まれる関数とKotlin 1.3で選択された関数が異なる場合があります。ただし、この挙動は、一部のコーナーケースでのみ発生するもので、非常にまれなケースであると考えています。 また、オーバーロードした関数は同様の挙動を見せ、最終的に互いを呼び出すため、これらの変更したところで、プログラムの動作には影響しないとも考えています。 様々なレベルでジェネティクスや多数のオーバーロードを使った手の込んだコードを記述する場合は、そのことに注意してください。 このようなすべてのケースは、上で言及した互換性ガイドに記載される予定です。
プレリリースに関する注意事項
下位互換性保証は、プレリリースバージョンには適用されません。 機能やAPIは、後続のリリースで変更する場合があります。 最終RCに達すると、プレリリースバージョンで生成したすべてのバイナリは、コンパイラによって使用禁止となります。その場合、1.4-Mxでコンパイルしたすべてのものを再コンパイルする必要があります。
試用方法
いつものように、play.kotl.inでKotlinをオンラインで試すことができます。
IntelliJ IDEAとAndroid Studio内で、Kotlinプラグインをバージョン1.4-M1に更新できます。 その方法についてはこれを参照してください。
プレリリースバージョンをインストールする前に作成された既存のプロジェクトを使用する場合、GradleまたはMavenでプレビューバージョン用にビルドを構成する必要があります。
コマンドラインコンパイラは、GitHubリリースページからダウンロードできます。
このリリースとともに公開された次のバージョンのライブラリを使用できます。
- kotlinx.atomicfu バージョン: 0.14.2-1.4-M1
- kotlinx.coroutines バージョン: 1.3.5-1.4-M1
- kotlinx.serialization バージョン: 0.20.0-1.4-M1
- ktor バージョン: 1.3.2-1.4-M1
リリースの詳細と互換性のあるライブラリの一覧は、ここから入手できます。
ご意見をお寄せください
バグを見つけた場合は、ぜひ課題トラッカー YouTrack にご報告ください。 すべての重要な課題については、次のKotlinリリースまで課題解決を延期することなく、最終リリース前に修正したいと思っています。
ご質問がある場合やディスカッションへの参加をご希望の場合は、Kotlin Slackの#eapチャンネルをお気軽に覗いてください(招待状リクエストはこちらから)。 このチャンネルでは、新しいプレビュービルドに関する通知を受け取ることもできます。
Let’s Kotlin!
ご協力いただいた外部貢献者
kotlin-reflect
に ProGuard構成を組み込んでいただいた Zac Sweers に、特別に感謝の意を表したいと思います。
また、このリリースに含められたPullリクエストは、次の外部貢献者からいただいたものです。ご協力いただきありがとうございました。
- Steven Schäfer
- Toshiaki Kameyama
- pyos
- Mads Ager
- Mark Punzalan
- Juan Chen
- Kristoffer Andersen
- Alfredo Delli Bovi
- Jinseong Jeon
- Jonathan Leitschuh
- Sebastian Schuberth
- Ivan Gavrilovic
- David Schreiber-Ranner
- Miguel Serra
- Alex Chmyr
- Fleshgrinder
- Aleksey Kladov
- Will Boyd
- Dominic Fischer
- Kenji Tomita
- Stéphane Nicolas
- Tillmann Berg
- Kevin Bierhoff
- Tsvetan
[原文] Original post in English is written by Sebastian Aigner