Kotlinの進化の原則
実用的な進化の原則
言語設計は盤石(cast in stone)なものですが、
その石は適度に柔らかく、
努力次第で、後から形を整えることができます。
Kotlin設計チーム
Kotlinは、プログラマーのための実用的なツールとして設計されています。言語の進化に関しては、その実用的な性質が以下の原則に集約されています:
- 言語を常にモダンに保つ。
- ユーザーとの継続的なフィードバックループを維持する。
- ユーザーが新しいバージョンへ簡単かつ快適にアップデートできるようにする。
これらはKotlinがどのように前進しているかを理解するための鍵となるため、これらの原則について詳しく説明します。
言語をモダンに保つ。システムには時間の経過とともにレガシーが蓄積されることを私たちは認識しています。かつて最先端だった技術も、今日では絶望的に古くなってしまうことがあります。ユーザーのニーズに応え続け、期待に沿った最新の状態を維持するために、私たちは言語を進化させなければなりません。これには、新しい機能を追加するだけでなく、本番環境での使用が推奨されなくなった古い機能を段階的に廃止し、レガシー化することも含まれます。
快適なアップデート。言語から機能を削除するなどの互換性のない変更は、適切な配慮なしに行われると、あるバージョンから次のバージョンへの移行に苦痛を伴う可能性があります。私たちは、そのような変更を常に十分前もって告知し、非推奨(deprecated)としてマークし、変更が行われる前に自動移行ツールを提供します。言語が変更される頃には、世界のほとんどのコードがすでに更新されており、新しいバージョンへの移行に問題がない状態にしたいと考えています。
フィードバックループ。非推奨のサイクルを経るには多大な労力が必要となるため、将来的に行う互換性のない変更の数を最小限に抑えたいと考えています。私たちは、自分たちの判断を信じるだけでなく、実際の現場で試してみることが設計を検証する最良の方法であると信じています。設計を盤石なものにする前に、実戦でテストされる必要があります。そのため、私たちはあらゆる機会を利用して、設計の初期バージョンを言語の製品版で利用可能にします。ただし、それらは プレステイブル(pre-stable) ステータスのいずれか、すなわち Experimental, Alpha, または Beta として提供されます。このような機能は安定しておらず、いつでも変更される可能性があり、それらの使用を選択(opt-in)したユーザーは、将来の移行問題に対処する準備ができていることを明示的に示していることになります。これらのユーザーから得られる貴重なフィードバックを収集して設計を繰り返し改善し、盤石なものにしていきます。
互換性のない変更
あるバージョンから別のバージョンにアップデートした際に、以前は動作していたコードが動作しなくなった場合、それは言語における 互換性のない変更(「破壊的変更(breaking change)」とも呼ばれます)です。何をもって「動作しなくなった」とするかについては、ケースによって議論の余地がありますが、確実に以下の内容が含まれます:
- コンパイルおよび実行が正常に行われていたコードが、(コンパイル時またはリンク時に)エラーで拒否されるようになる。これには、言語構造の削除や新しい制限の追加が含まれます。
- 正常に実行されていたコードが、例外をスローするようになる。
「グレーゾーン」に属する、あまり明白でないケースとしては、コーナーケースの処理が以前と異なる、以前とは異なる型の例外をスローする、リフレクションを通じてのみ観察可能な動作の変更、文書化されていない動作や未定義の動作の修正、バイナリアーティファクトの名称変更などが挙げられます。このような変更は、移行作業に劇的な影響を与えるほど重要な場合もあれば、重要でない場合もあります。
確実に互換性のない変更には当たらない例としては、以下のようなものがあります:
- 新しい警告(warning)の追加。
- 新しい言語構造の有効化、または既存の構造に対する制限の緩和。
- プライベート/内部(internal)API、およびその他の実装の詳細の変更。
「言語をモダンに保つ」および「快適なアップデート」という原則は、互換性のない変更が時には必要であることを示唆していますが、それらは慎重に導入されるべきです。私たちの目標は、ユーザーが快適にコードを移行できるよう、今後の変更を十分に前もって知らせることです。
理想的には、すべての互換性のない変更は、問題のあるコードに対して報告されるコンパイル時の警告(通常 非推奨警告(deprecation warning) と呼ばれます)を通じて告知され、自動移行支援ツールが伴うべきです。したがって、理想的な移行ワークフローは以下のようになります:
- バージョンA(変更が告知されるバージョン)にアップデートする
- 今後の変更に関する警告を確認する
- ツールを活用してコードを移行する
- バージョンB(変更が実施されるバージョン)にアップデートする
- まったく問題が発生しない
実際には、一部の変更はコンパイル時に正確に検出できないため、警告を報告できない場合がありますが、少なくともユーザーにはバージョンAのリリースノートを通じて、バージョンBで変更が行われることが通知されます。
コンパイラのバグへの対応
コンパイラは複雑なソフトウェアであり、開発者の最善の努力にもかかわらず、バグが存在します。コンパイラ自体が失敗したり、誤ったエラーを報告したり、明らかに失敗するコードを生成したりするバグは、迷惑で恥ずかしいものではありますが、その修正は互換性のない変更には当たらないため、修正は容易です。他のバグでは、失敗しないものの誤ったコードをコンパイラが生成してしまうことがあります。例えば、ソース内のエラーを見逃したり、単に間違った命令を生成したりする場合です。このようなバグの修正は、技術的には互換性のない変更(以前は正常にコンパイルできていたコードができなくなる)ですが、不正なコードパターンがユーザーコードに広がるのを防ぐために、できるだけ早く修正する方針です。これは「快適なアップデート」の原則をサポートするものと考えています。なぜなら、その問題に遭遇する可能性のあるユーザーが少なくなるからです。もちろん、これはリリースされたバージョンに現れてからすぐに発見されたバグにのみ適用されます。
意思決定
Kotlinの本来の作成者である JetBrains は、コミュニティの協力と Kotlin Foundation との連携のもと、Kotlinの進歩を推進しています。
Kotlinプログラミング言語へのすべての変更は、リード言語デザイナー(Lead Language Designer)(現在は Michail Zarečenskij)によって監督されています。リードデザイナーは、言語の進化に関連するすべての事項について最終決定権を持っています。さらに、完全に安定(stable)したコンポーネントに対する互換性のない変更は、Kotlin Foundation の下に設置された 言語委員会(Language Committee)(現在は Jeffrey van Gogh、Werner Dietl、および Michail Zarečenskij で構成)による承認を受ける必要があります。
言語委員会は、どの互換性のない変更を行うか、およびユーザーのアップデートを可能な限りシームレスにするためにどのような具体的な措置を講じるべきかについて最終的な決定を下します。その際、委員会は Language committee guidelines(言語委員会のガイドライン)の一連のルールに基づいています。
言語およびツールのリリース
2.0.0 のようなバージョンの安定版リリースは、通常、言語に主要な変更をもたらす 言語リリース と見なされます。通常、言語リリースの合間には、x.x.20 という番号が付けられた ツールリリース を公開します。
ツールリリースでは、ツールのアップデート(機能追加を含む場合が多い)、パフォーマンスの向上、およびバグ修正が行われます。このようなバージョン間では互換性を維持するように努めているため、コンパイラの変更は主に最適化と警告の追加/削除に留まります。プレステイブルな機能は、いつでも追加、削除、または変更される可能性があります。
言語リリースでは、多くの場合、新しい機能が追加され、以前に非推奨となった機能が削除または変更されることがあります。プレステイブルからステイブルへの機能の昇格(graduation)も、言語リリースで行われます。
EAPビルド
言語およびツールの安定版をリリースする前に、より迅速に反復し、コミュニティからフィードバックを収集するために、EAP(「Early Access Preview」の略)と呼ばれるプレビュービルドをいくつか公開します。言語リリースのEAPでは、通常、バイナリ形式の潜在的なバグがプレビュー期間を超えて残らないように、後の安定版コンパイラによって拒否されるバイナリを生成します。最終的なリリース候補(Release Candidate)版では、通常、この制限はありません。詳細は Participate in the Kotlin Early Access Preview をご覧ください。
プレステイブル(Pre-stable)機能
前述の「フィードバックループ」の原則に従い、私たちは設計を公開の場で繰り返し、一部の機能が プレステイブル ステータスのいずれかを持ち、変更されることが前提となっている 言語バージョンをリリースします。このような機能は、予告なくいつでも追加、変更、または削除される可能性があります。私たちは、不慣れなユーザーがプレステイブルな機能を誤って使用しないよう最善を尽くしています。通常、このような機能を使用するには、コード内またはプロジェクト構成において、何らかの明示的なオプトイン(承認)が必要となります。
Kotlinの言語機能は、以下のいずれかのステータスを持ちます:
探索と設計(Exploration and design)。新しい言語機能の導入を検討しています。これには、既存の機能とどのように統合されるかの議論、ユースケースの収集、および潜在的な影響の評価が含まります。この機能が解決する問題や、それが対応するユースケースについて、ユーザーからのフィードバックが必要です。可能であれば、これらのユースケースや問題がどの程度の頻度で発生するかを見積もることも有益です。通常、アイデアは YouTrack のイシューとして文書化され、そこで議論が続けられます。
KEEPでの議論(KEEP discussion)。その機能が言語に追加されるべきであると、私たちはかなり確信しています。動機、ユースケース、設計、およびその他の重要な詳細を KEEP と呼ばれるドキュメントで提供することを目指します。ユーザーからのフィードバックは、KEEPで提供されるすべての情報の議論に集中することを期待しています。
プレビュー中(In preview)。機能のプロトタイプが完成し、機能固有のコンパイラオプションを使用して有効にできます。機能の使用経験(コードベースへの統合のしやすさ、既存のコードとの相互作用、IDEサポートの問題や提案など)についてのフィードバックを求めます。機能の設計は大幅に変更される可能性があり、フィードバックに基づいて完全に取り消されることもあります。機能が プレビュー中 の場合、それは Experimental または Beta の 安定性レベル(stability levels) のいずれかです。
ステイブル(Stable)。その言語機能は、現在 Kotlin 言語の第一級市民です。後方互換性と、ツーリングによるサポートの提供を保証します。
撤回(Revoked)。提案を撤回し、その機能を Kotlin 言語には実装しません。プレビュー中 の機能が Kotlin に適していないと判断された場合、その機能を撤回することがあります。
Kotlin の言語提案とそのステータスの完全なリストを見る。
さまざまなコンポーネントのステータス
Kotlin/JVM、JS、Native コンパイラ、および各種ライブラリなど、Kotlin のさまざまなコンポーネントの安定性ステータスについての詳細はこちらをご覧ください。
ライブラリ
言語はエコシステムなしには成り立ちません。そのため、ライブラリのスムーズな進化を可能にするために細心の注意を払っています。
理想的には、ライブラリの新しいバージョンは、古いバージョンの「ドロップイン置換(そのまま入れ替え可能なもの)」として使用できるべきです。これは、アプリケーションが再コンパイルされない場合でも、バイナリ依存関係のアップグレードによって何も壊れてはならないことを意味します(これは動的リンクの下で可能です)。
一方で、これを実現するためには、コンパイラが分割コンパイルの制約下で、特定の アプリケーションバイナリインターフェース(ABI)の安定性保証を提供する必要があります。これが、言語のすべての変更がバイナリ互換性の観点から検討される理由です。
他方で、どの変更を安全に行えるかについては、ライブラリの作者の慎重さに大きく依存します。したがって、ライブラリの作者がソースの変更が互換性にどのように影響するかを理解し、ライブラリの API と ABI の両方を安定に保つための特定のベストプラクティスに従うことが重要です。ライブラリ進化の観点から言語の変更を検討する際、私たちは以下のような前提を置いています:
- ライブラリのコードは、常に public/protected 関数およびプロパティの戻り値の型を明示的に指定すべきであり、public API について型推論に頼るべきではありません。型推論のわずかな変更によって戻り値の型が意図せず変更され、バイナリ互換性の問題につながる可能性があります。
- 同じライブラリによって提供されるオーバーロードされた関数やプロパティは、本質的に同じことを行うべきです。型推論の変更により、呼び出し側でより正確な静的な型が判明し、オーバーロード解決の結果が変更される可能性があります。
ライブラリの作者は、@Deprecated および @RequiresOptIn アノテーションを使用して、API サーフェスの進化を制御できます。@Deprecated(level=HIDDEN) を使用すると、API から削除された宣言についてもバイナリ互換性を維持できることに注意してください。
また、慣例として、"internal" という名前のパッケージは public API とは見なされません。"experimental" という名前のパッケージにあるすべての API はプレステイブルと見なされ、いつでも変更される可能性があります。
私たちは、上記の原則に従って、安定したプラットフォーム向けの Kotlin 標準ライブラリ(kotlin-stdlib)を進化させています。その API のコントラクト(規約)への変更は、言語自体の変更と同じ手順を踏みます。
コンパイラオプション
コンパイラが受け付けるコマンドラインオプションも一種の public API であり、同じ考慮事項の対象となります。サポートされているオプション("-X" または "-XX" プレフィックスが付いていないもの)は言語リリースでのみ追加可能であり、削除する前には適切に非推奨にする必要があります。"-X" および "-XX" オプションは実験的なものであり、いつでも追加または削除される可能性があります。
互換性ツール
レガシーな機能が削除され、バグが修正されるにつれて、ソース言語は変化します。適切に移行されていない古いコードは、コンパイルできなくなる可能性があります。通常の非推奨サイクルでは、移行のための十分な期間が確保されますが、その期間が終了して安定版で変更が導入された後でも、移行されていないコードをコンパイルする方法があります。
互換性オプション
新しいバージョンの Kotlin で古いバージョンの動作をエミュレートし、互換性を維持するためのオプションを提供しています:
-language-version X.Y- Kotlin 言語バージョン X.Y に対する互換モード。それ以降に登場したすべての言語機能に対してエラーを報告します。-api-version X.Y- Kotlin API バージョン X.Y に対する互換モード。Kotlin 標準ライブラリのより新しい API を使用しているすべてのコード(コンパイラによって生成されたコードを含む)に対してエラーを報告します。
移行のための時間をさらに確保するため、最新の安定版に加えて、少なくとも 3 つ前の言語および API バージョンでの開発をサポートしています。
アクティブにメンテナンスされているコードベースは、完全な非推奨サイクルが完了するのを待たずに、できるだけ早くバグ修正の恩恵を受けることができます。現在、そのようなプロジェクトでは -progressive オプションを有効にすることで、ツールリリースであってもそのような修正を有効にできます。
すべてのオプションは、IDE、コマンドライン、および Gradle や Maven で利用可能です。
バイナリ形式の進化
最悪の場合でも手動で修正できるソースとは異なり、バイナリの移行ははるかに困難であるため、バイナリの場合、後方互換性は極めて重要です。バイナリに対する互換性のない変更は、アップデートを非常に不快なものにする可能性があるため、ソース言語の構文の変更よりもさらに慎重に導入されるべきです。
コンパイラの完全に安定したバージョンにおいて、デフォルトのバイナリ互換プロトコルは以下の通りです:
- すべてのバイナリは後方互換性(backwards compatible)があります。つまり、新しいコンパイラは古いバイナリを読み取ることができます(例:1.3 は 1.0 から 1.2 を理解できます)。
- 古いコンパイラは、新しい機能に依存するバイナリを拒否します(例:1.0 コンパイラはコルーチンを使用するバイナリを拒否します)。
- 望ましくは(ただし保証はできませんが)、バイナリ形式は次の言語リリースに対しては大部分が前方互換性(forwards compatible)を持ちますが、それ以降のリリースに対しては持ちません(新しい機能が使用されていない場合。例:1.9 は 2.0 のバイナリのほとんどを理解できますが、2.1 は理解できません)。
このプロトコルは、少し古いコンパイラを使用していてもプロジェクトが依存関係の更新を妨げられないように、快適なアップデートのために設計されています。
すべてのターゲットプラットフォームがこの安定性レベルに達しているわけではありませんが、Kotlin/JVM は達しています。
Kotlin klib バイナリ
Kotlin klib バイナリは、Kotlin 1.9.20 で Stable レベルに達しました。ただし、留意すべき互換性の詳細がいくつかあります:
- klib バイナリは、Kotlin 1.9.20 以降で後方互換性があります。例えば、2.0.x コンパイラは 1.9.2x コンパイラによって生成されたバイナリを読み取ることができます。
- 前方互換性は保証されてい ません。例えば、2.0.x コンパイラが 2.1.x コンパイラによって生成されたバイナリを読み取れることは保証されません。
Kotlin cinterop klib バイナリは、まだ Beta です。 現在、異なる Kotlin バージョン間での cinterop klib バイナリに対する特定の互換性保証を提供することはできません。
