32
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【PHP8.1】その配列、純粋配列?

Last updated at Posted at 2021-03-22

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_arrayarray_is_sequentialは、[2=>'a', 3=>'b']もシーケンシャルなので却下されました。
関数名is_zero_indexed_arrayarray_is_zero_indexedは、この用語はあまり使われていないので却下されました。

Alternate implementations

シグネチャはis_array_and_list(mixed $value): boolも検討されましたが、オブジェクトにfalseを返すのは直感的ではなく、またSplDoublyLinkedListArrayObjectなどに対する挙動が勘違いされる可能性があるため却下されました。
この関数は、連続したキーを持ち、なおかつ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からはあまり使われないんじゃないかという気がしないでもない。

32
16
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?