PHPのlist表現を使おう

PHPにはlist表現というものがあるが、積極的に使われているのをあんまり見ない。
これはおそらくlistの以外な便利さが詳細まで知られていないからかと思う。
そこでこの記事ではPHPのlistの使い方を細かく説明していくことにする。

基本的な使い方

list表現は代入式の左辺で使われ、右辺の配列の要素を一連の変数に代入する事が出来る。
そのlistのごく基本的な使い方の例が以下。

<?php
list($first, $second) = array(1, 2);

変数$firstに右辺の配列の0番目の要素が代入され、変数$secondに右辺の配列の1番目の要素が代入される。
listの外見は関数の呼び出しとほとんど変わらないので奇妙に見えるかもしれない。
上のコードは、以下とほぼ同じ意味を持つ。

<?php
$arr = array(1,2);
$first = $arr[0];
$second = $arr[1];

一行で済むlistを使った方がすっきりすることがわかると思う。

listはメソッドの引数をそのままプロパティに代入するというよくあるパターンにもそのまま使える。例えば以下のコード。

<?php
class Klass
{
    protected $foo, $bar, $hoge, $fuga;
    function inject($foo, $bar, $hoge, $fuga)
    {
        list($this->foo, $this->bar, $this->hoge, $this->fuga) = func_get_args();
    }
}

他にも配列の要素に一括代入することもできる。

<?php
$arr = array(1, 2, 3, 4);
list($arr[1], $arr[2]) = array('a', 'b'); // $arrはarray(1, 'a', 'b', 4)になる

これを応用して配列の要素をシャッフルする場合にも使える。
ここでは配列の0番目と2番目の要素を交換している。

<?php
$arr = array(1, 2, 3);
list($arr[2], $arr[0]) = array($arr[0], $arr[2]);

ただし、配列の最初とその次の要素の入れ替えを意図した以下のコードは正しく動かない。

<?php
$arr = array('x', 'y');
list($arr[1], $arr[0]) = $arr; // $arrはarray('y', 'y')となる

$arr[0]に$arr[1]が代入された後、$arr[1]に$arr[0]が代入されるので配列の要素が交換されない結果に終わる。
この動きの詳細はマニュアルのlistの該当ページを見てもらうとして、これを解決するには以下のように書く。

<?php
$arr = array('x', 'y');
list($arr[1], $arr[0]) = $arr + array();

空の配列をプラスすることで配列$arrの0番目と1番目の要素が無事に入れ替わるようになる。

エラーが出る場合

右辺の配列に代入すべき要素がない場合は、Undefined offsetエラーが出る。

<?php
list($a) = array(); // エラー
list($a, $b, $c) = array('x' => 1, 'y' => 2, 'z' => 3); // エラー

右辺の配列の中身が不定である場合は、配列の要素をシャッフルする例と同様に+演算子を使う。

<?php
function foo(Array $arr)
{
    list($a, $b) = $arr + array(1, 2);
    var_dump($a, $b);
}
foo(array(0)); //=> int(0) int(2)と表示される
foo(array('x' => 20)); //=> int(1) int(2)と表示される

これで右辺の配列の中身が不定であってもlist表現で安全に代入できる。

配列じゃなくてもイテレータでもよい

list表現での代入式の右辺は、配列以外にイテレータも使える。

<?php
$iterator = new ArrayIterator(array(1, 2, 3));
list($a, $b) = $iterator; // $a, $b にそれぞれ1, 2が代入される

イテレータでも代入すべき要素がない場合は同様にUndefined offsetエラーが出る。
これを解決するには+演算子を使えばよいが、イテレータに対しては+演算子は使えないので配列にキャストして使う。

<?php
$iterator = new ArrayIterator(array());
list($a, $b, $c) = (array)$iterator + array(1, 2, 3);

代入される変数の項が欠けていてもよい

list表現の項の変数は必ずしも必要ではなく、空でも良い。

<?php
list(, $second) = array(1, 2); // $secondに2が代入される

この場合、右辺の配列の1番目の要素以外はアクセスされない。例えば以下のようなコードでもエラーは出ない。

<?php
list(, $second, , ) = array(1 => 2);

ネストもできる

さらにlist表現はネストも可能である。

<?php
list(list($a), $b, list(list($c, $d))) = array(array(1), 2, array(array(3, 4)));

ちょっとした二次配列の要素もこれで一括して変数に代入できる。

あくまでも代入式

list表現は代入式の一部なので、代入式が使えるところでならどこででも使える。
例えば以下のように代入式の右辺の中の配列の項の中でも使える。

<?php
list($a) = array(list(, $b) = array(1, 2), 3);

有効に使える場面はそんなにないだろうけど、それでもある場面では役に立つこともあると思う。

最後に

ここまでlist表現について細かく説明してきた。
list表現は関数型言語のパターンマッチなどと比べると明らかに貧弱だが、それでも使いどころがそれなりにあると思うので、積極的に使っていきたい。