インフィニットループ 技術ブログ

2013年08月01日 (木)

著者 : emonkak

PHPのコレクション処理ライブラリUnderbar.phpの紹介

こんにちわちわ。
以前に便利な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を返すなんてことも当然できません。
そこでarrayTraversableなオブジェクトを同じように扱えて、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を使っていますが、他にArrayImplGeneratorImplがあります。

その他の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 requestIssuesの方にお願いします。

PHPのコレクション処理ライブラリUnderbar.phpの紹介

最後に社内勉強会でのUnderbar.php発表の様子とスライドです。

ブログ記事検索

このブログについて

このブログは、札幌市・仙台市の「株式会社インフィニットループ」が運営する技術ブログです。 お仕事で使えるITネタを社員たちが発信します!