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

PHPには配列と連想配列の区別がありません。
むしろPHPの配列は順序付きハッシュであり、その中でも数値のキーは特別に連番で扱える、みたいな扱いです。
他言語で言うところの配列はPHPにはありません。

```php
$arr = [
	'hoge' => 'fuga',
	'foo' =>  'bar',
	'x', // キーは0になる
	3=>'y',
	'z' // キーは4になる
];
```

しかしまあ区別したいよねってことで、[Add array_is_list](https://wiki.php.net/rfc/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と同等です。

```php
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`という名前にするつもりでしたが、[可能性の指摘を受けた](https://externals.io/message/112560#112565)ため名前を変更しました。
また`is_list`だと、PHP組み込みの[SplDoublyLinkedList](https://www.php.net/manual/ja/class.spldoublylinkedlist.php)等の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](https://www.php.net/manual/ja/function.is-array.php)に[引数を追加する](https://externals.io/message/112612#112612)というアイデアは却下されました。

```php
is_array($value, ZERO_INDEXED | ASSOCIATIVE | INTEGER_INDEXED)
```

基本的な型関数をシンプルにしておくことは言語の学習や読解にとって大事であり、またパフォーマンスに僅かな悪影響が発生します。

### Changes to PHP's type system

このRFCはPHPの型システムを変更しません。
後方互換の無い厳格な型システムへの変更は可能だとは思いますが、そこまでする必要のない選択であり、実装も難しいと思われます。


# 感想

助けて！
[SplFixedArray](https://www.php.net/manual/ja/class.splfixedarray.php)ちゃんが息をしていないの！！

RFCどころか[メーリングリスト](https://externals.io/message/109760)ですら誰一人SplFixedArrayに触れていないとかいう。

個人的にはPHPの配列と連想配列を厳密に区別する必要も意味もあまりないと思っているのですが、一般的には実はそうでもなかったみたいで、圧倒的多数の賛成によって導入が決定しました。
この関数によって、PHPにおける配列から、他言語で言うところの配列を区別することが可能になります。

まあPHP内においては区別する意味があまりないので、本文中にもあるように、JSON等で他言語とデータをやりとりするときに使うのが一般的な使用法となるでしょう。
むしろC言語側から使われて、PHPからはあまり使われないんじゃないかという気がしないでもない。