Kotlinの進化の原則
実用的な進化の原則
NOTE
言語設計は石に刻まれる
しかし、この石は比較的柔らかく、
少しの努力で後から形を変えることができる。
Kotlin設計チーム
Kotlinは、プログラマーにとって実用的なツールとなるように設計されています。言語の進化に関して、その実用性は以下の原則に集約されています。
時代に合わせて言語をモダンに保つ。
ユーザーとの継続的なフィードバックループを維持する。
ユーザーが新しいバージョンへ簡単かつ快適にアップデートできるようにする。
これらはKotlinがどのように前進しているかを理解する上で重要であるため、これらの原則について詳しく説明しましょう。
言語をモダンに保つ。システムは時間の経過とともにレガシーを蓄積することを認識しています。かつては最先端だったテクノロジーが、今日では絶望的に時代遅れになることがあります。私たちは、ユーザーのニーズに合致し、彼らの期待に応え続けるために、言語を進化させる必要があります。これには、新機能の追加だけでなく、本番環境での使用がもはや推奨されず、レガシーとなった古い機能の段階的な廃止も含まれます。
快適なアップデート。互換性のない変更(言語からの機能削除など)は、適切な注意を払わずに行われると、あるバージョンから次のバージョンへの移行が困難になる可能性があります。私たちは常に、そのような変更を十分に前もって告知し、非推奨とマークし、_変更が発生する前_に自動移行ツールを提供します。言語が変更されるまでに、世界中のほとんどのコードがすでにアップデートされており、新しいバージョンへの移行に問題がないことを目指しています。
フィードバックループ。非推奨化サイクルを経るにはかなりの労力が必要であるため、将来行う互換性のない変更の数を最小限に抑えたいと考えています。私たちは最善の判断を下すことに加えて、現実世界で試すことが設計を検証する最良の方法だと信じています。物事を確定する前に、それらが実戦で試されていることを望んでいます。そのため、私たちは設計の初期バージョンを言語のプロダクションバージョンで利用可能にする機会を最大限に活用しますが、それらはExperimental、Alpha、または Betaといった_プレ安定_なステータスで提供されます。このような機能は安定しておらず、いつでも変更される可能性があり、それらを使用することを選択したユーザーは、将来の移行問題に対処する準備ができていることを明示的に示しています。これらのユーザーは、設計を繰り返し改善し、盤石なものにするために私たちが収集する貴重なフィードバックを提供してくれます。
互換性のない変更
あるバージョンから別のバージョンにアップデートした際に、以前は動作していたコードが動作しなくなった場合、それは言語における_互換性のない変更_(「破壊的変更」とも呼ばれる)です。「動作しなくなった」が具体的に何を意味するのかについては、いくつかのケースで議論の余地がありますが、これには確実に以下のものが含まれます。
コンパイルおよび実行が正常に行われていたコードが、エラー(コンパイル時またはリンク時)によって拒否されるようになった場合。これには、言語構造の削除や新しい制限の追加が含まれます。
正常に実行されていたコードが、例外をスローするようになった場合。
「グレーゾーン」に属する、より不明瞭なケースには、コーナーケースの異なる処理、以前とは異なる種類の例外のスロー、リフレクションを通じてのみ観測可能な動作の変更、文書化されていないまたは未定義の動作の修正、バイナリ成果物の名前変更などが含まれます。時にこのような変更は極めて重要であり、移行体験に劇的に影響を及ぼしますが、時には些細なものでもあります。
互換性のない変更に確実には該当しない例をいくつか挙げます。
新しい警告の追加。
新しい言語構造の有効化、または既存の構造に対する制限の緩和。
プライベート/内部APIおよびその他の実装詳細の変更。
「言語をモダンに保つ」および「快適なアップデート」の原則は、互換性のない変更が時には必要であるものの、慎重に導入されるべきであることを示唆しています。私たちの目標は、ユーザーが将来の変更を十分に前もって認識し、コードを快適に移行できるようにすることです。
理想的には、すべての互換性のない変更は、問題のあるコードで報告されるコンパイル時警告(通常は_非推奨警告_と呼ばれる)を通じて告知され、自動移行支援を伴うべきです。したがって、理想的な移行ワークフローは次のようになります。
バージョンA(変更が告知されるバージョン)にアップデートする
今後の変更に関する警告を確認する
ツールからの助けを借りてコードを移行する
バージョンB(変更が発生するバージョン)にアップデートする
- 全く問題がないことを確認する
実際には、一部の変更はコンパイル時に正確に検出できないため、警告を報告できない場合がありますが、少なくともユーザーはバージョンAのリリースノートを通じて、バージョンBで変更が発生することを通知されます。
コンパイラバグへの対処
コンパイラは複雑なソフトウェアであり、開発者の最善の努力にもかかわらず、バグが存在します。コンパイラ自体が失敗したり、誤ったエラーを報告したり、明らかに失敗するコードを生成したりするバグは、煩わしく、しばしば当惑させるものですが、修正が互換性のない変更を構成しないため、簡単に修正できます。その他のバグは、コンパイラが失敗しない間違ったコードを生成する可能性があります。たとえば、ソース内のエラーを見逃したり、単に誤った命令を生成したりするなどです。このようなバグの修正は、技術的には互換性のない変更(一部のコードは以前は正常にコンパイルされていたが、今後はそうではない)ですが、悪質なコードパターンがユーザーコードに広がるのを防ぐため、できるだけ早く修正する傾向があります。私たちの意見では、これは「快適なアップデート」の原則を支持するものであり、なぜなら、より少ないユーザーがその問題に遭遇する機会を持つからです。もちろん、これはリリースバージョンに登場した直後に発見されたバグにのみ適用されます。
意思決定
Kotlinのオリジナル作成者であるJetBrainsは、コミュニティの助けを借り、Kotlin Foundationとの協力のもと、その進歩を推進しています。
Kotlinプログラミング言語へのすべての変更は、リード言語デザイナー(現在はMichail Zarečenskij)によって監督されています。リードデザイナーは、言語の進化に関するすべての事柄において最終的な決定権を持ちます。
加えて、完全に安定したコンポーネントに対する互換性のない変更は、Kotlin Foundationの下で指名された言語委員会(現在はJeffrey van Gogh、Werner Dietl、Michail Zarečenskijで構成)によって承認される必要があります。
言語委員会は、どの互換性のない変更を行うか、そしてユーザーのアップデートを可能な限りシームレスにするためにどのような具体的な措置を講じるかについて最終的な決定を下します。
その際、言語委員会ガイドラインのセットに依拠しています。
言語リリースとツールリリース
2.0.0のようなバージョンを持つ安定版リリースは、通常、言語に大きな変更をもたらす_言語リリース_と見なされます。
通常、言語リリースの間に、x.x.20と番号付けされた_ツールリリース_を公開しています。
ツールリリースは、ツールのアップデート(多くの場合、機能を含む)、パフォーマンスの向上、およびバグ修正をもたらします。
私たちはこれらのバージョン間の互換性を維持するよう努めており、コンパイラへの変更はほとんどが最適化と警告の追加/削除に限定されます。
プレ安定機能はいつでも追加、削除、または変更される可能性があります。
言語リリースでは、新しい機能が追加されたり、以前非推奨化された機能が削除または変更されたりすることがよくあります。
プレ安定機能から安定版への機能の卒業も、言語リリースで行われます。
EAPビルド
言語およびツールリリースの安定版をリリースする前に、EAP("Early Access Preview"の略)と呼ばれる複数のプレビュービルドを公開し、これにより、より迅速に反復処理を行い、コミュニティからのフィードバックを収集することができます。
言語リリースのEAPは通常、後で安定版コンパイラによって拒否されるバイナリを生成し、バイナリ形式の潜在的なバグがプレビュー期間を超えて存続しないようにします。
最終リリース候補は通常、この制限を受けません。
プレ安定機能
上記のフィードバックループの原則に従い、私たちはオープンに設計を反復し、一部の機能が_プレ安定_なステータスであり、_変更されることが想定されている_言語のバージョンをリリースします。
このような機能は、いつでも警告なしに追加、変更、または削除される可能性があります。
私たちは、無意識のユーザーがプレ安定機能を誤って使用できないように最善を尽くしています。
通常、このような機能は、コードまたはプロジェクト設定のいずれかで、何らかの明示的なオプトインを必要とします。
Kotlin言語機能には、以下のいずれかのステータスがあります。
探求と設計 (Exploration and design)。言語に新しい機能の導入を検討している段階です。これには、既存の機能との統合方法の議論、ユースケースの収集、潜在的な影響の評価が含まれます。
この機能が解決する問題と対処するユースケースについて、ユーザーからのフィードバックが必要です。
可能であれば、これらのユースケースと問題の発生頻度を見積もることも有益です。
通常、アイデアはYouTrackの課題として文書化され、そこで議論が継続されます。
KEEP議論 (KEEP discussion)。その機能が言語に追加されるべきであるとかなり確信している段階です。
私たちは、その動機、ユースケース、設計、およびその他の重要な詳細を、_KEEP_と呼ばれるドキュメントで提供することを目指しています。
ユーザーからのフィードバックは、KEEPで提供されるすべての情報の議論に焦点を当てることを期待しています。
プレビュー中 (In preview)。機能のプロトタイプが準備されており、機能固有のコンパイラオプションを使用して有効にできます。
コードベースへの統合の容易さ、既存コードとの相互作用、IDEサポートの問題や提案など、その機能の使用経験に関するフィードバックを求めています。
フィードバックに基づいて、機能の設計が大幅に変更されたり、完全に撤回されたりする可能性があります。機能が_プレビュー中_の場合、安定性レベルを持ちます。
安定版 (Stable)。言語機能がKotlin言語の第一級市民になりました。
私たちはその後方互換性とツールサポートの提供を保証します。
撤回済み (Revoked)。提案を撤回し、Kotlin言語にその機能を実装しないことにしました。
_プレビュー中_の機能がKotlinに適していない場合、撤回することがあります。
Kotlin言語の提案とそのステータスの一覧はこちらをご覧ください。
各コンポーネントのステータス
Kotlin/JVM、JS、Nativeコンパイラ、および様々なライブラリなど、Kotlinの各コンポーネントの安定性ステータスについて詳しく学びましょう。
ライブラリ
言語はエコシステムなしでは成り立たないため、私たちはライブラリのスムーズな進化を可能にすることに特に注意を払っています。
理想的には、ライブラリの新しいバージョンは、古いバージョンの「ドロップイン置換」として使用できます。
これは、バイナリ依存関係をアップグレードしても、アプリケーションが再コンパイルされない場合でも、何も壊れないことを意味します(これは動的リンクで可能です)。
一方で、これを実現するためには、コンパイラは分離コンパイルの制約の下で、特定の_Application Binary Interface_ (ABI)の安定性保証を提供する必要があります。
そのため、言語のすべての変更は、バイナリ互換性の観点から検討されます。
他方で、ライブラリ開発者がどの変更が安全であるかについて慎重であるかどうかに大きく依存します。
したがって、ライブラリ開発者がソースコードの変更が互換性にどのように影響するかを理解し、ライブラリのAPIとABIの両方を安定させるための特定のベストプラクティスに従うことが重要です。
ここでは、ライブラリの進化の観点から言語の変更を検討する際に、私たちが設定するいくつかの前提条件を挙げます。
ライブラリコードは、パブリック/プロテクト関数およびプロパティの戻り型を常に明示的に指定し、パブリックAPIで型推論に依存しないようにする必要があります。型推論の微妙な変更が、意図せず戻り型を変更させ、バイナリ互換性の問題を引き起こす可能性があります。
同じライブラリによって提供されるオーバーロードされた関数およびプロパティは、本質的に同じことを行うべきです。型推論の変更により、呼び出しサイトでより正確な静的型が判明し、オーバーロード解決の変更を引き起こす可能性があります。
ライブラリ開発者は、@Deprecated
および @RequiresOptIn
アノテーションを使用して、APIサーフェスの進化を制御できます。@Deprecated(level=HIDDEN)
は、APIから削除された宣言であってもバイナリ互換性を維持するために使用できることに注意してください。
また、慣例として、「internal」と名付けられたパッケージはパブリックAPIとは見なされません。
「experimental」と名付けられたパッケージに存在するすべてのAPIはプレ安定と見なされ、いつでも変更される可能性があります。
私たちは、安定したプラットフォーム向けのKotlin標準ライブラリ(kotlin-stdlib
)を、上記の原則に従って進化させています。
そのAPIの契約への変更は、言語自体の変更と同じ手順を経て行われます。
コンパイラオプション
コンパイラが受け入れるコマンドラインオプションも一種のパブリックAPIであり、同じ考慮事項が適用されます。
サポートされているオプション(-X
または -XX
接頭辞を持たないもの)は言語リリースでのみ追加でき、削除される前に適切に非推奨化されるべきです。
-X
および -XX
オプションは実験的なものであり、いつでも追加および削除できます。
互換性ツール
レガシー機能が削除され、バグが修正されるにつれて、ソース言語は変化し、適切に移行されていない古いコードはもはやコンパイルされない可能性があります。
通常の非推奨化サイクルは、移行のための十分な時間を提供し、それが終了して変更が安定版にリリースされた後でも、移行されていないコードをコンパイルする方法がまだあります。
互換性オプション
新しいバージョンが古いバージョンの動作をエミュレートするように、-language-version X.Y
および -api-version X.Y
オプションを提供しています。移行のための時間を増やすため、最新の安定版に加えて、以前の3つの言語バージョンおよびAPIバージョンをサポートしています。
活発にメンテナンスされているコードベースは、完全な非推奨化サイクルが完了するのを待つことなく、可能な限り早くバグ修正を受けることで恩恵を受けることができます。
現在、そのようなプロジェクトは -progressive
オプションを有効にすることで、ツールリリースでもそのような修正を有効にすることができます。
すべてのオプションは、コマンドラインだけでなく、Gradle および Maven でも利用可能です。
バイナリ形式の進化
最悪の場合でも手作業で修正できるソースとは異なり、バイナリは移行がはるかに難しく、そのためバイナリの場合には後方互換性が極めて重要になります。
バイナリに対する互換性のない変更は、アップデートを非常に困難にする可能性があり、ソース言語の構文に対する変更よりもさらに慎重に導入されるべきです。
完全に安定したバージョンのコンパイラの場合、デフォルトのバイナリ互換性プロトコルは以下の通りです。
すべてのバイナリは後方互換性があります。つまり、新しいコンパイラは古いバイナリを読み取ることができます(例:1.3は1.0から1.2までを理解します)。
古いコンパイラは、新しい機能に依存するバイナリを拒否します(例:1.0コンパイラはコルーチンを使用するバイナリを拒否します)。
好ましくは(ただし保証はできません)、バイナリ形式は次の言語リリースに対してほとんど前方互換性がありますが、それ以降のリリースに対しては互換性がありません(新しい機能が使用されていない場合など、例: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)です。
現在、cinterop klibバイナリについて、異なるKotlinバージョン間での具体的な互換性保証はできません。