Kotlinの進化の原則
実用的な進化の原則
言語の設計は石に刻まれる
しかし、この石は適度に柔らかく
少しの努力で後から形を変えることができる
Kotlin デザインチーム
Kotlinはプログラマーにとって実用的なツールとして設計されています。言語の進化に関しては、その実用的な性質が以下の原則によって捉えられています。
- 言語を常に最新に保つ。
- ユーザーとの継続的なフィードバックループを維持する。
- ユーザーにとって新しいバージョンへの更新を簡単かつ快適にする。
これらはKotlinがどのように前進しているかを理解する上で重要であるため、これらの原則を詳しく見ていきましょう。
言語を最新に保つ。システムは時間とともにレガシーを蓄積することを認識しています。かつて最先端だった技術が、今日ではどうしようもなく時代遅れになることがあります。私たちは、ユーザーのニーズに関連し、期待に応え続けるために、言語を進化させる必要があります。これには、新しい機能の追加だけでなく、本番環境での使用が推奨されなくなり、レガシーとなった古い機能を段階的に廃止することも含まれます。
快適なアップデート。言語からの削除のような非互換な変更は、適切な注意を払わずに行われると、あるバージョンから次のバージョンへの移行が困難になる可能性があります。私たちは、そのような変更を常に十分前もって発表し、非推奨としてマークし、変更が発生する前に自動移行ツールを提供します。言語が変更される頃には、世界のほとんどのコードがすでに更新され、新しいバージョンへの移行に問題がないようにしたいと考えています。
フィードバックループ。非推奨サイクルを経験するにはかなりの努力が必要なため、将来行う非互換な変更の数を最小限に抑えたいと考えています。私たちは最善の判断を使用することに加えて、現実世界で試すことが設計を検証する最良の方法だと信じています。物事を石に刻む前に、実地で十分にテストしたいのです。このため、私たちは設計の初期バージョンを言語のプロダクションバージョンで利用可能にするあらゆる機会を利用していますが、それはExperimental、Alpha、またはBetaといったプレ安定版のステータスのいずれかで行われます。そのような機能は安定しておらず、いつでも変更される可能性があり、それらの使用をオプトインするユーザーは、将来の移行問題に対処する準備ができていることを明示的に示すためにそうします。これらのユーザーは、設計を繰り返し改善し、それを堅固なものにするために私たちが収集する貴重なフィードバックを提供してくれます。
非互換な変更
あるバージョンから別のバージョンに更新したときに、以前は動作していたコードが動作しなくなった場合、それは言語における非互換な変更(「破壊的変更」と呼ばれることもあります)です。 「動作しなくなった」が正確に何を意味するかについては、場合によっては議論の余地がありますが、以下のものは間違いなく含まれます。
- コンパイルおよび実行が正常に完了していたコードが、エラー(コンパイル時またはリンク時)で拒否されるようになった場合。これには、言語構造の削除や新しい制限の追加が含まれます。
- 通常に実行されていたコードが、例外をスローするようになった場合。
「グレーゾーン」に属する、より不明瞭なケースには、コーナーケースの異なる処理、以前とは異なる型の例外のスロー、リフレクションを通じてのみ観測可能な動作の変更、文書化されていないまたは未定義の動作の変更、バイナリ成果物の名前変更などがあります。 時には、そのような変更が極めて重要であり、移行体験に劇的に影響することもありますが、時には些細なこともあります。
明らかに非互換な変更ではないものの例をいくつか挙げます。
- 新しい警告の追加。
- 新しい言語構造の有効化、または既存の構造の制限の緩和。
- private/internal APIやその他の実装の詳細の変更。
「言語を最新に保つ」および「快適なアップデート」の原則は、非互換な変更が時として必要であるが、慎重に導入されるべきであることを示唆しています。私たちの目標は、ユーザーがコードを快適に移行できるように、今後の変更を事前に十分に認識させることです。
理想的には、すべての非互換な変更は、問題のあるコードで報告されるコンパイル時警告(通常、「非推奨警告」と呼ばれます)を通じて発表され、自動移行支援を伴うべきです。 したがって、理想的な移行ワークフローは次のとおりです。
- バージョンAにアップデートする(変更が発表されるバージョン)
- 今後の変更に関する警告を確認する
- ツールを使用してコードを移行する
- バージョンBにアップデートする(変更が発生するバージョン)
- 一切問題が発生しないことを確認する
実際には、コンパイル時に正確に検出できない変更もあるため、警告を報告できない場合がありますが、少なくともユーザーには、バージョンAのリリースノートを通じて、バージョンBで変更があることが通知されます。
コンパイラのバグへの対処
コンパイラは複雑なソフトウェアであり、開発者の最善の努力にもかかわらず、バグが存在します。 コンパイラ自体が失敗したり、誤ったエラーを報告したり、明らかに失敗するコードを生成したりするバグは、煩わしく、しばしば恥ずかしいものではありますが、修正が簡単です。なぜなら、修正が非互換な変更を構成しないからです。 他のバグは、コンパイラが失敗しない間違ったコードを生成する可能性があります。たとえば、ソース内の一部のエラーを見逃したり、単に間違った命令を生成したりする場合です。 そのようなバグの修正は、技術的には非互換な変更(以前は正常にコンパイルできたコードが、今はコンパイルできなくなるため)ですが、不適切なコードパターンがユーザーコード全体に広がるのを防ぐために、可能な限り早く修正する傾向にあります。 私たちの意見では、これは「快適なアップデート」の原則を支持します。なぜなら、問題に遭遇する可能性のあるユーザーが少なくなるからです。 もちろん、これはリリースバージョンに登場してすぐに発見されたバグにのみ適用されます。
意思決定
Kotlinの元の作成者であるJetBrainsは、コミュニティの助けを借りて、またKotlin財団と協力して、その進歩を推進しています。
Kotlinプログラミング言語へのすべての変更は、リード言語デザイナー(現在はMichail Zarečenskij)によって監督されています。 リードデザイナーは、言語の進化に関するすべての事柄について最終的な決定権を持ちます。 さらに、完全に安定したコンポーネントへの非互換な変更は、Kotlin財団の下で指定された言語委員会(現在はJeffrey van Gogh、Werner Dietl、Michail Zarečenskijで構成)によって承認される必要があります。
言語委員会は、どの非互換な変更が行われるか、そしてユーザーのアップデートを可能な限りシームレスにするためにどのような具体的な措置が取られるかについて最終決定を下します。 その際、委員会は一連の言語委員会ガイドラインに依拠しています。
言語とツールのリリース
2.0.0のようなバージョン番号を持つ安定版リリースは、通常、言語の主要な変更をもたらす言語リリースと見なされます。 通常、言語リリースとリリース言語の間には、x.x.20と番号付けされたツールリリースを公開しています。
ツールリリースは、ツール(しばしば機能を含む)、パフォーマンスの改善、バグ修正の更新をもたらします。 私たちはそのようなバージョン間での互換性を維持するよう努めているため、コンパイラの変更は主に最適化と警告の追加/削除に限られます。 プレ安定版機能は、いつでも追加、削除、または変更される可能性があります。
言語リリースでは、しばしば新機能が追加され、以前に非推奨になった機能が削除または変更される場合があります。 プレ安定版から安定版への機能の卒業も言語リリースで行われます。
EAPビルド
言語およびツールの安定版リリースに先立ち、私たちはEAP(「早期アクセスプレビュー」の略)と呼ばれる多数のプレビュービルドを公開し、より迅速に反復し、コミュニティからフィードバックを収集できるようにしています。 言語リリースのEAPは通常、後で安定版コンパイラによって拒否されるバイナリを生成し、バイナリ形式の潜在的なバグがプレビュー期間を超えて残らないようにします。 最終的なリリース候補には、通常この制限はありません。
プレ安定版機能
上記の「フィードバックループ」の原則に従い、私たちは設計を公開で反復し、一部の機能がプレ安定版のステータスのいずれかであり、変更されることが想定されている言語のバージョンをリリースします。 そのような機能は、いつでも警告なしに追加、変更、または削除される可能性があります。 私たちは、無意識のユーザーがプレ安定版機能を誤って使用しないように最善を尽くします。 そのような機能は通常、コード内またはプロジェクト設定内で何らかの明示的なオプトインを必要とします。
Kotlin言語機能は、以下のいずれかのステータスを持つことができます。
検討と設計 (Exploration and design)。私たちは言語に新機能を導入することを検討しています。 これには、既存の機能との統合方法の議論、ユースケースの収集、潜在的な影響の評価が含まれます。 ユーザーから、この機能が解決する問題と対処するユースケースに関するフィードバックが必要です。 可能な限り、これらのユースケースと問題の発生頻度を推定することも有益です。 通常、アイデアはYouTrackのIssueとして文書化され、そこで議論が続けられます。
KEEPに関する議論 (KEEP discussion)。私たちはその機能が言語に追加されるべきであるとかなり確信しています。 私たちは、その動機、ユースケース、設計、およびその他の重要な詳細を
KEEP
と呼ばれる文書で提供することを目指しています。 ユーザーからのフィードバックは、KEEP
で提供されたすべての情報に関する議論に焦点を当てることを期待しています。プレビュー中 (In preview)。機能のプロトタイプが準備されており、機能固有のコンパイラオプションを使用して有効にすることができます。 私たちは、その機能がコードベースにどれだけ簡単に統合されるか、既存のコードとどのように相互作用するか、IDEサポートの問題や提案など、機能の使用経験に関するフィードバックを求めています。 フィードバックに基づいて、機能の設計が大幅に変更されたり、完全に撤回されたりする可能性があります。機能がプレビュー中の場合、安定性レベルがあります。
安定版 (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」オプションは実験的であり、いつでも追加および削除できます。
互換性ツール
レガシー機能が削除され、バグが修正されるにつれて、ソース言語が変更され、適切に移行されていない古いコードはコンパイルできなくなる可能性があります。 通常の非推奨サイクルでは、移行のために十分な期間が与えられ、それが終了し、変更が安定版でリリースされた後でも、移行されていないコードをコンパイルする方法があります。
互換性オプション
新しいバージョンが互換性のために古いバージョンの動作をエミュレートする-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で安定版レベルに達しました。 ただし、いくつかの互換性の詳細に注意する必要があります。
- klibバイナリは、Kotlin 1.9.20以降、後方互換性があります。たとえば、2.0.xコンパイラは1.9.2xコンパイラによって生成されたバイナリを読み取ることができます。
- 前方互換性は保証されません。たとえば、2.0.xコンパイラが2.1.xコンパイラによって生成されたバイナリを読み取れることは保証されません。
Kotlin cinterop klibバイナリはまだベータ版です。 現在、cinterop klibバイナリについては、異なるKotlinバージョン間での特定の互換性保証を提供することはできません。