こんにちわちわ。
以前に便利なVimのOperatorをマスターするを書きましたemonkakです。
今回は私が個人的に開発を進めていて以前に社内勉強会で発表しました、PHPのコレクション処理ライブラリのUnderbar.phpの紹介をしたいと思います。
PHPのarrayなんとか関数は使い勝手が悪い
PHPには配列処理のための関数がたくさんありますが、array_map()の引数の順番が変だったりarrayがそもそもオブジェクトではないのでメソッドチェインができなかったり使い勝手が良くありません。
$xs = range(1, 10);
$twice = function($n) { return $n * 2; };
array_slice(array_map($twice, $xs), 0, 3);
// [2, 4, 6]
また、これらの関数はarray専用なので、IteratorのようなTraversableなオブジェクトを引数に取ることはできませんし、処理を遅延してIteratorを返すなんてことも当然できません。
そこでarrayとTraversableなオブジェクトを同じように扱えて、Iteratorを利用して遅延リストを返すことができるライブラリが欲しいですよね。
さらにPHP5.5から導入されたGeneratorも使えるといいですね!
遅延リストの恩恵
ここで一度、遅延リストの恩恵について考えてみます。
0からの連番をたくさん返すIteratorの$ysがあります。
ここから偶数の要素を二乗して5つだけ取り出したいとします。
foreachを使って自前でループしてbreakするのが効率的なのは当然ですが、ここでは高階関数を使って書いてみます。
$even = function($n) { return $n % 2 === 0 };
$square = function($n) { return $n * $n; };
array_slice(array_map($square, array_filter(iterator_to_array($ys, false), $even)), 0, 5);
// [0, 4, 16, 36, 64]
このコードには効率上の問題が2つあります。
- 要素5つ分のメモリ空間で十分なのに
Iteratorの長さ分だけ空間を必要とする - 今回必要としない5個目以降の要素についても
$squareが呼びだされてしまう
自前でループを書いて処理すればいいのですが面倒ですね。
それなら以下のような感じでIteratorをチェインして遅延リストとして処理してしまいましょう。
$xs = new TakeIterator(new MapIterator(new FilterIterator($ys, $even), $square), 5); iterator_to_array($xs); // [0, 4, 16, 36, 64]
これでわざわざループを書かなくても済みますし、効率上の問題も解決しました。
Underbar.phpの実装もまさにこのようになっています。
Underbar.phpとは
Underbar.phpはこれまでに述べた問題を解決するために開発されたPHPのコレクション処理のためのライブラリです。
その名の通りUnderscore.jsにインスパイアされたライブラリですが、移植というわけではないのでそれほど互換性は重要視していません。
詳しい使い方は公式ドキュメントを読んで頂くとして、
遅延リストを利用してフィボナッチ数列を10個取り出す例を引用してみます。
use Underbar\IteratorImpl as _;
// Fibonacci sequence
echo _::chain([1, 1])
->iterate(function($pair) { return [$pair[1], _::sum($pair)]; })
->map(function($pair) { return $pair[0]; })
->take(10)
->join(); // 1,1,2,3,5,8,13,21,34,55
今回の例ではIteratorを返すIteratorImplを使っていますが、他にArrayImplとGeneratorImplがあります。
その他のPHPのコレクション処理ライブラリ達
PHPのarrayが扱いづらい問題はPHPerの共通見解のようでUnderbar.php以外にもいくつかコレクション処理ライブラリが開発されています。
大きくUnderscore.js系とLINQ系に分けることができます。
Underscore.js系
自分も1つ増やしてしまったわけですが、ちょっと乱立気味ですね。
本来こういった機能は言語の組み込みにするなり、何か統一を図るべきなのかもしれません。
まあPHPなのでしょうがないですね!
この中だと本家のUnderscore.jsが即時実行する実装なのもあって、遅延実行できる実装はありません。
これがUnderbar.phpの最初の開発の動機でした。
LINQ系
LINQ系のライブラリも結構数がありますが、最後発のGinqが設計が洗練されていて非常に良くできています。このスライドで大体の雰囲気を掴めるかと思います。
LINQ好きはGinq使っておけば間違いないですね。
Underbar.phpにはないjoinによるデータの結合やソートの合成などもあるのでおすすめです。
まとめ
PHPの配列を扱う関数は非常に使い勝手が悪いので、様々なコレクション処理のためのライブラリが開発されています。
しかし、中でも遅延リストを扱えるものは少ないので、そういった意味ではUnderbar.phpが選択肢の一つになりうると思います。
Underbar.phpは今後社内でも利用しようという動きがありますが、まだ誕生したばかりのライブラリなので不具合や要望等あればpull requestかIssuesの方にお願いします。


