PHP でジェネリックを使用できない理由

in Vlog

(jp) =

<!–

–>

ジェネリックと PHP に関して、舞台裏で何が起こっているのかを深く掘り下げていきます。 ジェネリックが PHP の第一級市民としてまだサポートされていない理由を理解することは非常に興味深いことであり、非常に重要です。

始めましょう。

ジェネリックスは PHP には来ていません。 それが昨年のニキータの結論でした。 それは単に実行できませんでした。

ニキータがそう言った理由を理解するには、ジェネリックをどのように実装できるかを調べる必要があります。 一般に、これを行うには 3 つの方法があります。 ジェネリックをサポートするプログラミング言語は、ほとんどの場合、これら 3 つの方法のいずれかを使用します。

最初のものは呼ばれます 単形ジェネリック. このコレクションの例を示したこのシリーズの最初の投稿に戻りましょう。

class StringCollection extends Collection

    public function offsetGet(mixed $key): string 
      


class UserCollection extends Collection

    public function offsetGet(mixed $key): User 
      

コレクションが必要な型ごとにコレクション クラスの実装を手動で作成できることを説明しました。 手作業が多く、コードも多くなりますが、うまくいくでしょう。

モノモーフィングされたジェネリックはまさに​​これを行いますが、自動化された方法で、舞台裏で行われます。 実行時に、PHP はジェネリック Collection クラスについては認識していませんが、2 つ以上の特定の実装については認識していません。

$users = new Collection<User>();


$slugs = new Collection<string>();

モノモーフィゼーション ジェネリックは、完全に有効なアプローチです。 たとえば、Rust はそれらを使用します。 利点の 1 つは、パフォーマンスが大幅に向上することです。実行時にジェネリック型チェックが不要になるため、コードを実行する前にすべてが分割されます。

しかし、これはすぐに、PHP のモノモーフィングされたジェネリックに関する問題につながります。 PHP には、1 つのジェネリック クラスを複数の特定の実装に分割する Rust のような明示的なコンパイル手順はありません。 それに加えて、モノモーフィングされたジェネリックは、いくつかの違いがある同じクラスの複数のコピーを作成しているため、かなりの量のメモリを必要とします。 これは、コンパイルされた Rust バイナリーにとってはそれほど大きな問題ではないかもしれませんが、中央のポイントであるサーバーから実行される PHP コードにとっては深刻な問題です。 毎秒数百または数千のリクエストを処理している可能性があります。

次のオプションは 具体化されたジェネリック. これは、ジェネリック クラスがそのまま保持され、型情報が実行時にオンザフライで評価される実装です。 C# と Kotlin ではジェネリックが具体化されており、PHP の現在の型システムに最も近いのは、PHP がすべての型チェックを実行時に行うためです。 ここでの問題は、具体化されたジェネリックが機能するために膨大な量のコア コードのリファクタリングが必要になることです。また、実行時にますます多くの型チェックを行うため、パフォーマンスのオーバーヘッドが忍び寄ることが想像できます。

それは、実行時にジェネリックを完全に無視するという最後のオプションです。 彼らがそこにいないかのように振る舞います。 結局のところ、たとえばコレクション クラスの一般的な実装は、とにかくあらゆる種類の入力で機能します。

したがって、実行時にすべてのジェネリック型チェックを無視しても問題はありません。

まあ、それほど速くはありません。 実行時にジェネリック型を無視する – それは呼び出されます タイプ消去 ところで、Java と Python はこれを行いますが、PHP にいくつかの問題を引き起こします。

その 1 つは、PHP は検証に型を使用するだけでなく、型情報を使用して値をある型から別の型にオンザフライで変換することです。

function add(int $a, int $b): int 

    return $a + $b;


add('1', '2') 

PHP がこの「文字列」コレクションのジェネリック型を無視し、それに誤って整数を追加した場合、ジェネリック型が消去された場合、それについて警告することはできません。

$slugs = new Collection<string>();

$slugs[] = 1; 

型の消去に関する 2 つ目の、より重要な問題は、型がなくなっていることです。 ジェネリック型が実行時に消去されるのに、なぜジェネリック型を追加するのでしょうか?

静的アナライザーを使用してコードを実行する前に、すべての型定義がチェックされるため、Java と Python では意味があります。 たとえば Java は、コードのコンパイル時に組み込みの静的アナライザーを実行します。 PHP が単純に実行しないこと: コンパイルのステップがなく、組み込みの静的型チェッカーがないことは確かです。

一方で…型チェックのすべての利点は、以前の投稿で説明したものです。 これらは、PHP の組み込みのランタイム タイプチェッカーに由来するものではありません。 PHP の型チェッカーが何か問題があることを通知するまでには、既にコードを実行しています。 型エラーは基本的にプログラムをクラッシュさせます。

代わりに、型チェックの付加価値のほとんどは、コードを実行する必要のない静的アナライザーから得られます。 プログラマーであるあなたが十分な型情報を提供する限り、実行時の型エラーが発生しないようにするのが得意です。 コードにバグがまったくないわけではありませんが、完全に静的にチェックされ、実行中に型エラーが発生しない PHP コードを作成することは可能です。 それに加えて、コードを書いている間に得られるすべての静的な洞察があります。 これは、あらゆる型システムの中で最も価値のある部分であり、実行時の型チェックとは何の関係もありません。

では、実際に実行時の型チェックが必要なのでしょうか? これが、現在 PHP にジェネリックを追加できない主な理由です。PHP が実行時にジェネリック型を検証するには、複雑すぎるか、リソースを大量に消費します。

次回は、このシリーズの最後の投稿です。

tpyoに気づきましたか? PR を送信して修正することができます。 このブログの最新情報を知りたい場合は、私をフォローしてください。 ツイッター または私のニュースレターを購読してください:

//platform.twitter.com/widgets.js

関連記事

前の投稿
犬は栗を食べてもいい?
次の投稿
ウィスコンシン州で最も深い湖を発見