PHPには配列と連想配列の区別がありません。
むしろPHPの配列は順序付きハッシュであり、その中でも数値のキーは特別に連番で扱える、みたいな扱いです。
他言語で言うところの配列はPHPにはありません。
$arr = [
'hoge' => 'fuga',
'foo' => 'bar',
'x', // キーは0になる
3=>'y',
'z' // キーは4になる
];
しかしまあ区別したいよねってことで、Add array_is_listというRFCが提出されました。
既に受理されており、PHP8.1にて実装されます。
Add array_is_list(array $array): bool
Introduction
PHPの配列は、整数キーと文字列キーの両方をサポートしており、さらに順序が保証されているという珍しい型です。
変数値が配列であるかのチェックは簡単ですが、その配列は連想配列だったり、オフセットの欠けた配列だったり、順序が異なっていたりする場合があります。
配列のキーが連続した整数であるか否かを確認することは、モジュールに渡すデータおよびモジュールから返されたデータのどちらにとっても有用なものです。
またシリアライザにとっても、配列と連想配列を区別するための効率的なチェック方法があると便利です。
たとえばjson_encodeは、配列を['0':0, '2':1, '1':1]
とシリアライズしたいのか、[0, 1, 2]
とシリアライズしたいのかを判定したいでしょう。
Proposal
新しい関数array_is_list(array $array):bool
を追加します。
配列のキーが0から始まる数値で順番に並んでいる場合にtrue
を返します。
それ以外の配列であればfalse
を返します。
配列でない場合はTypeError
をthrowします。
このRFCは、PHPの型システムを変更するものではなく、新しい型を追加することもありません。
この関数は、以下のPolyfillと同等です。
function array_is_list(array $array): bool {
$expectedKey = 0;
foreach ($array as $i => $_) {
if ($i !== $expectedKey) { return false; }
$expectedKey++;
}
return true;
}
$x = [1 => 'a', 0 => 'b'];
var_export(array_is_list($x)); // false キーが順番ではない
unset($x[1]);
var_export(array_is_list($x)); // true キーが0開始の配列である
// 単純にarray_valuesで比較するとNaNの罠がある
$x = ['key' => 2, NAN];
unset($x['key']);
var_export($x === array_values($x)); // false
var_export($x); // array (0 => NAN)
var_export(array_is_list($x)); // true キーが0開始の配列である
// 配列ではない
array_is_list(new stdClass()); // TypeError
array_is_list(null); // TypeError
正しいPolyfillを書くためには注意して罠を避ける必要があります。
たとえばarray_values($array) === $array
はNaNを含む配列でfalseになり、array_keys($array) === range(0, count($array) - 1)
は空の配列で正しくなりません。
ネイティブ実装ではマクロHT_IS_PACKED(array) && HT_IS_WITHOUT_HOLES(array)
を使えばほとんどの配列を正しく判定可能で、これは既にjson_encodeで使われています。
Proposed PHP Version
PHP8.1。
Discussion
Possibility of naming conflicts with future vector-like types
将来ベクトル型が導入されたときに命名が衝突しない?
この関数は元々is_list
という名前にするつもりでしたが、可能性の指摘を受けたため名前を変更しました。
またis_list
だと、PHP組み込みのSplDoublyLinkedList等のList型と混同してしまう可能性があります。
array_is_list
は、配列しか受け付けない点で他の多くのarray_*
関数と共通しています。
投票
2021/01/06から2021/01/20にかけて投票が行われ、賛成41反対1で可決されました。
Rejected Features
却下・拒否された仕様や実装。
Alternate names
関数名is_sequential_array
・array_is_sequential
は、[2=>'a', 3=>'b']
もシーケンシャルなので却下されました。
関数名is_zero_indexed_array
・array_is_zero_indexed
は、この用語はあまり使われていないので却下されました。
Alternate implementations
シグネチャはis_array_and_list(mixed $value): bool
も検討されましたが、オブジェクトにfalseを返すのは直感的ではなく、またSplDoublyLinkedList
やArrayObject
などに対する挙動が勘違いされる可能性があるため却下されました。
この関数は、連続したキーを持ち、なおかつ0始まりの配列に対してのみtrueを返します。
Adding flags to is_array()
is_arrayに引数を追加するというアイデアは却下されました。
is_array($value, ZERO_INDEXED | ASSOCIATIVE | INTEGER_INDEXED)
基本的な型関数をシンプルにしておくことは言語の学習や読解にとって大事であり、またパフォーマンスに僅かな悪影響が発生します。
Changes to PHP's type system
このRFCはPHPの型システムを変更しません。
後方互換の無い厳格な型システムへの変更は可能だとは思いますが、そこまでする必要のない選択であり、実装も難しいと思われます。
感想
助けて!
SplFixedArrayちゃんが息をしていないの!!
RFCどころかメーリングリストですら誰一人SplFixedArrayに触れていないとかいう。
個人的にはPHPの配列と連想配列を厳密に区別する必要も意味もあまりないと思っているのですが、一般的には実はそうでもなかったみたいで、圧倒的多数の賛成によって導入が決定しました。
この関数によって、PHPにおける配列から、他言語で言うところの配列を区別することが可能になります。
まあPHP内においては区別する意味があまりないので、本文中にもあるように、JSON等で他言語とデータをやりとりするときに使うのが一般的な使用法となるでしょう。
むしろC言語側から使われて、PHPからはあまり使われないんじゃないかという気がしないでもない。