PHP8.2でPHPの乱数がとても改善するという話をしましたが、RFCが採択された後になって幾つか問題や不足が出てきたため、それらの対応を行うフォローアップのRFCが提出されました。
ということで以下は該当のRFC、Random Extension Improvementの紹介です。
7つの提案があったのですが、全て採択されています。
なお、このRFCはPHP8.2向けなので、2022/12/08にリリースされたPHP8.2から既に使用可能です。
PHP RFC: Random Extension Improvement
Introduction
先日のRandom Extension 5.xにおいて、投票が始まったあとで幾つか問題が発生ました。
Engine implementations are not final
Random Extensionでネイティブに提供されたRNGエンジンのクラスには、finalがついていませんでした。
これはつまりネイティブクラスをextendsしたクラスが作れてしまうわけですが、前回のRFCで述べたように、ユーザランドエンジンは実行効率がネイティブクラスに劣ります。
この効率劣化はメソッドをオーバーライドしなかった場合でも発生するため問題になります。
エクステンションは既にRandom\Engine
インターフェイスを提供しているため、ネイティブクラスがfinalになったとしてもこちらを使えば問題ありません。
これはAPI設計のミスであり、乱数エンジンのネイティブ実装はfinalにすべきです。
Random\SerializableEngine is not useful
Random\SerializableEngine
は、Serializableインターフェイスがまだ生きていたころの名残です。
現在のPHPは、シリアライズ可能か否かは__serialize()
メソッドが存在するかだけで判断されるようになったため、このインターフェイスは不要になりました。
従って、Random\SerializableEngine
を削除します。
現在SerializableEngine
をimplementsしている乱数エンジンはこれを実装しなくなりますが、シリアライズ可能かどうかの判定には影響しません。
Random\Engine\CombinedLCG is low quality
Random\Engine\CombinedLCG
はlcg_value関数でのみ使われている乱数エンジンです。
しかし、このアルゴリズムは古めかしく、そして低品質なものです。
lcg_value
を維持するため内部実装はそのままにしますが、クラスは廃止して外部からは使用できないようにします。
There is no equivalent of array_rand()
array_randに相当する機能がRandomizerには存在しません。
かつてのRFCによると、これは意図的なものでした。
しかし、調査したところではarray_randは多くのライブラリで使用されていたため、Randomizerでも使用可能にするべきでしょう。
そこで、Randomizer::pickArrayKeys(array $array, int $num): array
メソッドを追加します。
一見array_rand(array $array, int $num = 1): int|string|array
と互換性が無いように見えますが、以下のようにすると、同じ結果を得ることができます。
$array = ['foo', 'bar', 'baz'];
// これまで
mt_srand(1234, MT_RAND_PHP);
$single = array_rand($array); // 0
$multiple = array_rand($array, 2); // [1, 2]
// 今後
$engine = new Random\Engine\Mt19937(1234, MT_RAND_PHP);
$randomizer = new Random\Randomizer($engine);
$single = $randomizer->pickArrayKeys($array, 1)[0]; // 0
$multiple = $randomizer->pickArrayKeys($array, 2); // [1, 2]
"string" means a binary
PHPにおいてstring
はバイナリを意味します。
これはマルチバイト文字列を扱う際に問題であり、たとえばstr_shuffleを日本語に使うとめちゃくちゃになります。
従って、str_shuffle
に相当するメソッドRandomizer::shuffleString()
はRandomizer::shuffleBytes()
に改名した方が、より適切な名称となるでしょう。
Engine classnames are not precise
エンジンのクラス名が曖昧であり、アルゴリズムを明確にするために改名する必要があります。
Random\Engine\PCG64
→ Random\Engine\PcgOneseq128XslRr64
Random\Engine\MersenneTwister
→ Random\Engine\Mt19937
PCG is not so famous
PCGは優れたアルゴリズムであり、素晴らしいランダム性とパフォーマンスを持っています。
しかしその知名度はXorshiftに比べるとずっと劣ります。
そのため、PCGがよくわからないからMT19937を使おうといった選択を避けるため、以前から候補として挙がっていたRandom\Engine\Xoshiro256StarStar
を追加で実装します。
Proposal
各議題に対し、それぞれ投票を行います。
Engine implementations are not final
賛成18、反対1の賛成多数で可決されました。
Random\SerializableEngine is not useful
賛成18、反対0の賛成多数で可決されました。
Random\Engine\CombinedLCG is low quality
賛成18、反対0の賛成多数で可決されました。
There is no equivalent of array_rand()
賛成12、反対2の賛成多数で可決されました。
"string" means a binary
賛成16、反対1の賛成多数で可決されました。
Engine classnames are not precise
賛成14、反対1の賛成多数で可決されました。
PCG is not so famous
賛成13、反対1の賛成多数で可決されました。
Backward Incompatible Changes
以下のクラス名は、使用できなくなります。
・Random\Engine\Mt19937
・Random\Engine\PcgOneseq128XslRr64
・Random\Engine\Xoshiro256StarStar
以下のクラス名は、使用可能になります。
・Random\Engine\CombinedLCG
・Random\Engine\MersenneTwister
・Random\Engine\PCG64
・Random\SerializableEngine
Proposed PHP Version
PHP8.2
RFC Impact
特になし。
Patches and Tests
References
感想
乱数について最初のRFCで足りなかったところを補足・変更するRFCとなります。
PHP internalはどうも提案の時点では無風なのに投票を開始した途端に活気づくという悪癖がありがちで、たとえばConstants in traitsを作った人もたいへんつらい的なことを言っていました。
(なんかもっと直接的な発言を見たような気がするのだが見つけられなかった)
ということで、Random Extensionについても投票開始後にfinalにしたほうがいいんじゃねとか色々指摘が入り、改善RFCの提案に至ったようです。
このRFCにもやっぱり投票開始後に多少の物言いが入りましたが、無事に全ての提案が通ったため、PHP8.2から既に使用することができます。
めでたし。