【PHP8.2】PHPの乱数がさらに改善される

[PHP8.2でPHPの乱数がとても改善する](https://qiita.com/rana_kualu/items/ab82a9c6dbd2e7c6edd0)という話をしましたが、RFCが採択された後になって幾つか問題や不足が出てきたため、それらの対応を行うフォローアップのRFCが提出されました。

ということで以下は該当のRFC、[Random Extension Improvement](https://wiki.php.net/rfc/random_extension_improvement)の紹介です。
7つの提案があったのですが、全て採択されています。

なお、このRFCはPHP8.2向けなので、2022/12/08にリリースされたPHP8.2から既に使用可能です。


# [PHP RFC: Random Extension Improvement](https://wiki.php.net/rfc/random_extension_improvement)

## Introduction

先日の[Random Extension 5.x](https://wiki.php.net/rfc/rng_extension)において、投票が始まったあとで幾つか問題が発生ました。

### 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](https://www.php.net/lcg_value)関数でのみ使われている乱数エンジンです。
しかし、このアルゴリズムは古めかしく、そして低品質なものです。

`lcg_value`を維持するため内部実装はそのままにしますが、クラスは廃止して外部からは使用できないようにします。

### There is no equivalent of array_rand()

[array_rand](http://www.php.net/array_rand)に相当する機能がRandomizerには存在しません。
かつてのRFCによると、これは意図的なものでした。
しかし、調査したところではarray_randは多くのライブラリで使用されていたため、Randomizerでも使用可能にするべきでしょう。

そこで、`Randomizer::pickArrayKeys(array $array, int $num): array`メソッドを追加します。
一見`array_rand(array $array, int $num = 1): int|string|array`と互換性が無いように見えますが、以下のようにすると、同じ結果を得ることができます。

```php
$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](http://www.php.net/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

https://github.com/php/php-src/pull/8094


## References

https://externals.io/message/117939


# 感想

乱数について[最初のRFC](https://wiki.php.net/rfc/rng_extension)で足りなかったところを補足・変更するRFCとなります。

PHP internalはどうも[提案の時点では無風](https://externals.io/message/117295)なのに[投票を開始した途端に活気づく](https://externals.io/message/117939)という悪癖がありがちで、たとえば[Constants in traits](https://wiki.php.net/rfc/constants_in_traits)を作った人も[たいへんつらい](https://www.slideshare.net/shinjiigarashi/10-php-trait)的なことを言っていました。
(なんかもっと直接的な発言を見たような気がするのだが見つけられなかった)

ということで、Random Extensionについても投票開始後に[finalにしたほうがいいんじゃね](https://externals.io/message/117939#117955)とか色々指摘が入り、改善RFCの提案に至ったようです。

このRFCにもやっぱり[投票開始後に多少の物言い](https://externals.io/message/118163)が入りましたが、無事に全ての提案が通ったため、PHP8.2から既に使用することができます。
めでたし。