Streamsは,遅延評価とデータの関数型変換を提供することで,C++標準ライブラリのコンテナとアルゴリズムの使い勝手を向上する,C++14ライブラリである。mapやfilter, reduceといった一般的な操作を多数サポートする。
ストリームとは,特定の順序を持ったデータセットを抽象化したものである。要求されたときにのみ次のオペレーションにデータを渡すという遅延形式で,ストリームパイプラインを通じて転送されるそのようなデータに対して,さまざまな操作を適用することができる。ストリームのオペレータには,3つの主な種類がある。
- ジェネレータ(Generator)はストリームの生成手段であり,ストリームの発生源となる。
- 中間オペレータ(Intermediate operator)は入力用ストリームと戻り用ストリームを指定して,ストリームパイプラインの末端に何らかの追加操作を適用する。
- 終端オペレータ(Terminal operator)はストリームをクローズし,すべてのストリーム操作を実行してストリームパイプラインを評価する。
ジェネレータと中間オペレータはいずれも,ストリーム上の何かの終端オペレーションがコールされるまでは評価されない。
次の例は1から10までの平方を加算するものだ。
int total = stream::MakeStream::counter(1) .map([] (int x) { return x * x; }) .limit(10) .sum();
次の例はオブジェクトから特定の値を抽出して,集合を介してそれを処理する方法である。
std::vector widgets = /* ... */ std::set ids = stream::MakeStream::from(widgets) .map(&Widget::getId) .to_set();
mapやfilter, reduceといった関数型の操作の他にも,Streamsは,さまざまな集合操作(和, 積, 差分)や部分合計,隣接差分,その他の便利な操作をサポートする。
InfoQはStreamsの作者であるJonah Scheinerman氏にコンタクトを取り,いくつかの点について聞いてみた。
Streamsを開発しようと思った一番の動機は何でしたか?
C++には標準的なツールやアルゴリズムのライブラリがいくつかあります。それらは素晴らしいのですが,構文上のオーバーヘッドの過剰なことが,その表現力を損ねています。例えば,共通集合を取得する場合を考えてみてください。std::set left = /* ... */ std::set right = /* ... */ std::set result; std::set_intersection(left.begin(), left.end(), right.begin(), right.end(), std::inserter(result, result.end()));これでは構文が複雑すぎて,左右の集合の積を取ってそれを別の集合に収めたいという,処理の意図が分かりません。こんな具合に,もっとシンプルに書けた方がよいですよね。stream::Stream left = /* ... */ stream::Stream right = /* ... */ std::set result = left.intersect_with(right).as_set();このようなケースを考えながら,Streamsの設計をしました。その他の標準ライブラリのアルゴリズムでも,begin()やend()が過剰に使われていますし,std::inserterやstd::back_inserterなどのラッパを使うのも煩雑です。さらに,データ集合に一連の変換を行おうとすると,次の処理で使用するためだけの目的で,一時的なデータのvectorをたくさん作らなければならず,ますます厄介なことになります。標準ライブラリ関数は非常に汎用的ではありますが,あまりにも多くのケースにおいて冗長過ぎるのです。移動セマンティクス(move secantics)のない世界用に設計された標準ライブラリの機能のせいで,C++11のメリットが失われています。
これを開発する大きなきっかけになったのは,Java 8にStreamsが導入されたことです (これ自体もPythonのitertoolsに触発されたのですが)。Javaに遅延評価ができるのなら,C++はもっとよいものを持つべきだと思いませんか?
C+には他にも,関数型や遅延評価を実現する試みが存在します(Boost.LambdaやBoost.Phoenixなど)。そのようなものに代えて,開発者にStreamsを選択させるような長所,あるいはメリットを説明して頂けますか?
Streamsの大きなメリットは,現代的な言語機能を利用していることと,その構文上の単純さです。Boost.LocalePhoenixは確かに面白いと思いますが,ラムダ以前のC++のためのハッキング作品といった感があります。オペレータオーバーロードやキーワードに似せた関数名(例えばforではなくfor_)には,使いにくさを感じますね。Boost.LocaleLamdaについては,C++11以降ではメリットがありません。
現時点でStreamsの競合相手だと思うのはRx(Reactive Expression)だけですが,Streamsとは目標が少し違います(Rxは並行性を,Streamsは構文と可読性を重視して設計されています)。いずれにせよ,Rxというライバルの存在はお互いにとってよいことですし,もっと優れた機能を開発しようという意欲を持たせてくれます。
Streamsは何を目指しているのでしょう? ライブラリのロードマップはあるのでしょうか?
取り合えずはコードをクリーンにすること,C++互換のライブラリにすること,ベンチマークとテスト,ドキュメントの充実などを考えています。その後は並列実行性や,他の人たちがストリーム演算子を簡単に追加できるような柔軟性を持たせることなどが目標です。最終的には,遅延評価をC++標準にする方法を見付けることができたらいいですね。それはStreamsではないかも知れませんが,その可能性もあります。
StreamsはGitHubで,MITライセンスの下にリリースされている。