Kotlinの進化の原則
実用的な進化の原則
言語設計は石に刻まれているが、
この石は適度に柔らかく、
多少の努力で後から形を変えることができる。
Kotlin Design Team
Kotlin はプログラマーのための実用的なツールとして設計されています。言語の進化に関しては、その実用的な性質は、以下の原則によって捉えられています。
- 言語を常に最新の状態に保つ。
- ユーザーとの継続的なフィードバックループを維持する。
- ユーザーが新しいバージョンに簡単かつ快適にアップデートできるようにする。
これは Kotlin がどのように前進しているかを理解するための鍵となるため、これらの原則について詳しく説明します。
言語を常に最新の状態に保つ。私たちは、システムが時間の経過とともにレガシーを蓄積することを認識しています。かつて最先端だった技術は、今日ではどうしようもなく時代遅れになる可能性があります。ユーザーのニーズに関連性を保ち、ユーザーの期待に応えられるように、言語を進化させる必要があります。これには、新機能の追加だけでなく、本番環境での使用が推奨されなくなり、レガシーとなった古い機能を段階的に廃止することも含まれます。
快適なアップデート。言語から何かを削除するなどの互換性のない変更は、適切な注意を払わずに実行すると、あるバージョンから次のバージョンへの移行が苦痛になる可能性があります。私たちは常に、そのような変更を事前に告知し、変更が起こる前に、非推奨としてマークし、自動移行ツールを提供します。言語が変更されるときまでに、世界のコードのほとんどがすでに更新され、新しいバージョンへの移行に問題がないようにしたいと考えています。
フィードバックループ。非推奨サイクルを経るにはかなりの労力が必要となるため、将来的に行う互換性のない変更の数を最小限に抑えたいと考えています。最善の判断を下すことに加えて、実際に試してみることが設計を検証する最良の方法であると考えています。物事を決定する前に、実戦テストを行いたいと考えています。そのため、設計の初期バージョンを言語の本番バージョンで利用できるようにするあらゆる機会を利用していますが、Experimental, Alpha, or Beta のいずれかの pre-stable ステータスにします。このような機能は安定しておらず、いつでも変更される可能性があり、それらを使用することを選択したユーザーは、将来の移行問題に対処する準備ができていることを明示的に示すためにそうします。これらのユーザーは、設計を反復して確固たるものにするために収集する貴重なフィードバックを提供してくれます。
互換性のない変更
あるバージョンから別のバージョンにアップデートしたときに、以前は動作していたコードが動作しなくなった場合、それは言語における 互換性のない変更 (「破壊的な変更」と呼ばれることもあります) です。場合によっては、「動作しなくなった」が正確に何を意味するのかについて議論の余地があるかもしれませんが、次のものが含まれることは間違いありません。
- コンパイルおよび実行が正常に行われていたコードが、エラーで拒否されるようになった(コンパイル時またはリンク時)。これには、言語構造の削除や新しい制限の追加が含まれます。
- 正常に実行されていたコードが、例外をスローするようになった。
「グレーゾーン」に属するあまり明確でないケースには、コーナーケースの処理方法の変更、以前とは異なる型の例外のスロー、リフレクションを通じてのみ観察可能な動作の変更、ドキュメント化されていない、または未定義の動作の変更、バイナリ成果物の名前変更などが含まれます。場合によっては、このような変更が非常に重要であり、移行エクスペリエンスに劇的な影響を与えることもあれば、取るに足らないこともあります。
互換性のない変更に該当しないものの例をいくつか示します。
- 新しい警告の追加。
- 新しい言語構造の有効化、または既存の言語構造の制限の緩和。
- private/internal API およびその他の実装の詳細の変更。
言語を常に最新の状態に保つことと、快適なアップデートの原則は、互換性のない変更が時には必要であることを示唆していますが、慎重に導入する必要があります。私たちの目標は、ユーザーがコードを快適に移行できるように、今後の変更を事前にユーザーに認識してもらうことです。
理想的には、互換性のない変更はすべて、問題のあるコードで報告されるコンパイル時の警告(通常は 非推奨警告 と呼ばれます)を通じて通知され、自動移行支援が伴います。したがって、理想的な移行ワークフローは次のとおりです。
- バージョン A (変更が通知される場所) にアップデートする
- 今後の変更に関する警告が表示される
- ツールからの支援を受けてコードを移行する
- バージョン B (変更が発生する場所) にアップデートする
- 問題は一切発生しない
実際には、一部の変更はコンパイル時に正確に検出できないため、警告を報告できませんが、少なくともユーザーはバージョン A のリリースノートを通じて、変更がバージョン B で発生することを通知されます。
コンパイラのバグへの対処
コンパイラは複雑なソフトウェアであり、開発者の最大限の努力にもかかわらず、バグがあります。コンパイラ自体が失敗したり、誤ったエラーを報告したり、明らかに失敗するコードを生成したりするバグは、煩わしく、しばしば当惑させられますが、修正によって互換性のない変更が発生することはないため、修正は簡単です。他のバグは、コンパイラが失敗しない誤ったコードを生成する可能性があります。たとえば、ソース内のエラーを見逃したり、単に間違った命令を生成したりします。このようなバグの修正は、技術的には互換性のない変更です (一部のコードは以前は正常にコンパイルされていましたが、現在はコンパイルされません)。ただし、ユーザーコード全体に不良コードパターンが広がるのを防ぐために、できるだけ早く修正する傾向があります。私たちの意見では、これは快適なアップデートの原則をサポートします。なぜなら、問題に遭遇するユーザーの数が減るからです。もちろん、これはリリースされたバージョンに登場してから間もなく発見されたバグにのみ適用されます。
意思決定
Kotlin の最初の作成者である JetBrains は、コミュニティの助けを借りて、Kotlin Foundation とのコラボレーションにより、その進捗を推進しています。
Kotlin プログラミング言語に対するすべての変更は、Lead Language Designer (現在は Michail Zarečenskij) によって監督されています。リードデザイナーは、言語の進化に関するすべての事項について最終決定権を持っています。さらに、完全に安定したコンポーネントに対する互換性のない変更は、Kotlin Foundation の下で指定された Language Committee (現在は Jeffrey van Gogh、Werner Dietl、Michail Zarečenskij で構成されています) によって承認される必要があります。
Language Committee は、どの互換性のない変更を行うか、およびユーザーのアップデートを可能な限りシームレスにするためにどのような正確な対策を講じる必要があるかについて最終的な決定を下します。その際、一連の Language committee guidelines に依存しています。
言語とツールのリリース
2.0.0 などのバージョンを含む安定版リリースは、通常、言語に大きな変更をもたらす 言語リリース と見なされます。通常、言語リリースの間に、x.x.20 の番号が付けられた ツールリリース を公開します。
ツールリリースでは、ツール (多くの場合、機能を含む)、パフォーマンスの改善、およびバグ修正が更新されます。このようなバージョン間の互換性を維持するように努めているため、コンパイラへの変更はほとんどが最適化と警告の追加/削除です。プレ安定版の機能は、いつでも追加、削除、または変更できます。
言語リリースでは、多くの場合、新機能が追加され、以前に非推奨とされた機能が削除または変更される可能性があります。プレ安定版から安定版への機能の卒業も言語リリースで行われます。
EAP ビルド
言語およびツールの安定版リリースをリリースする前に、イテレーションを高速化し、コミュニティからのフィードバックを収集できる EAP (「Early Access Preview」) と呼ばれるプレビュービルドをいくつか公開します。言語リリースの EAP は通常、バイナリ形式のバグがプレビュー期間よりも長く存続しないようにするために、後で安定版コンパイラによって拒否されるバイナリを生成します。最終リリース候補は通常、この制限を受けません。
プレ安定版の機能
上記のフィードバックループの原則に従って、設計をオープンに反復し、一部の機能が pre-stable ステータスのいずれかを持つ言語のバージョンをリリースし、変更されることになっています。このような機能は、いつでも、警告なしに追加、変更、または削除できます。プレ安定版の機能が疑いを持たないユーザーによって誤って使用されないように最善を尽くします。このような機能は通常、コード内またはプロジェクト構成内のいずれかで、何らかの明示的なオプトインを必要とします。
Kotlin 言語機能は、次のステータスのいずれかを持つことができます。
-
探索と設計。言語に新機能の導入を検討しています。これには、既存の機能との統合方法について話し合い、ユースケースを収集し、潜在的な影響を評価することが含まれます。この機能が解決する問題と、それが対処するユースケースについて、ユーザーからのフィードバックが必要です。可能な限り、これらのユースケースと問題が発生する頻度を見積もることも有益です。通常、アイデアは YouTrack の問題として文書化され、そこで議論が続きます。
-
KEEP の議論。機能は言語に追加する必要があると確信しています。KEEP と呼ばれるドキュメントで、動機、ユースケース、設計、およびその他の重要な詳細を提供することを目指しています。ユーザーからのフィードバックは、KEEP で提供されるすべての情報について議論することに焦点を当てることを期待しています。
-
プレビュー中。機能のプロトタイプが完成し、機能固有のコンパイラオプションを使用して有効にすることができます。コードベースへの統合の容易さ、既存のコードとの相互作用、IDE サポートの問題や提案など、機能の使用経験に関するフィードバックを求めています。機能の設計は大幅に変更される可能性があり、フィードバックに基づいて完全に取り消される可能性があります。機能が プレビュー中 の場合、安定性レベル があります。
-
安定。言語機能は、Kotlin 言語の第一級市民になりました。後方互換性と、ツールサポートを提供することを保証します。
-
取り消し済み。提案を取り消し、Kotlin 言語に機能を実装しません。Kotlin に適していない場合は、プレビュー中 の機能を取り消す場合があります。
Kotlin 言語の提案とそのステータスの完全なリストを参照してください。
さまざまなコンポーネントのステータス
Kotlin/JVM、JS、および Native コンパイラ、およびさまざまなライブラリなど、Kotlin のさまざまなコンポーネントの安定性ステータス について詳しく学んでください。
ライブラリ
言語はエコシステムがなければ何も意味がないため、スムーズなライブラリの進化を可能にすることに特別な注意を払っています。
理想的には、ライブラリの新しいバージョンは、古いバージョンの「ドロップイン置換」として使用できます。これは、アプリケーションが再コンパイルされなくても(これは動的リンクの下で可能です)、バイナリ依存関係をアップグレードしても何も壊れないことを意味します。
一方では、これを実現するために、コンパイラは個別のコンパイルの制約の下で、特定の Application Binary Interface (ABI) の安定性の保証を提供する必要があります。そのため、言語のすべての変更は、バイナリ互換性の観点から検討されます。
他方では、多くのことが、どの変更を加えるのが安全であるかについて、ライブラリの作成者が注意しているかどうかにかかっています。したがって、ライブラリの作成者がソースコードの変更が互換性にどのように影響するかを理解し、ライブラリの API と ABI の両方を安定させるために特定のベストプラクティスに従うことが重要です。ライブラリの進化の観点から言語の変更を検討する場合、次のような仮定を行います。
- ライブラリのコードは、常に public/protected 関数およびプロパティの戻り値の型を明示的に指定する必要があります。したがって、public API の型推論に決して依存しないでください。型推論のわずかな変更により、戻り値の型が意図せずに変更され、バイナリ互換性の問題が発生する可能性があります。
- 同じライブラリによって提供されるオーバーロードされた関数およびプロパティは、本質的に同じことを行う必要があります。型推論の変更により、呼び出しサイトでより正確な静的型が認識されるようになり、オーバーロード解決の変更が発生する可能性があります。
ライブラリの作成者は、@Deprecated
および @RequiresOptIn
アノテーションを使用して、API サーフェスの進化を制御できます。@Deprecated(level=HIDDEN)
は、API から削除された宣言でもバイナリ互換性を維持するために使用できることに注意してください。
また、慣例により、「internal」という名前のパッケージは public API と見なされません。「experimental」という名前のパッケージにあるすべての API は、プレ安定版と見なされ、いつでも変更される可能性があります。
上記の原則に従って、安定版プラットフォームの Kotlin Standard Library (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 で 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 バージョン間の具体的な互換性保証を提供することはできません。