はじめに
Dart 1.9のリースを目前にして、Dev版にて非同期版のgeneratorがサポートされました。
DartのGeneratorでフィボナッチ数列 (同期版)で約束した通り非同期版を紹介します。
なお、実行確認はversion:1.9.0-dev.10.2 (Thu Mar 05 09:49:17 2015) on "windows_x64"、ビルド番号44260で行っています。
実装
フィボナッチ数列生成関数(非同期Generator版)
Try Dart!にはフィボナッチ数を再帰計算で求めるサンプルがありますが、ここではフィボナッチ数列を求めます。
一つ前ともう一つ前の値を覚えておいてループの中で足し算するのが素直ですね。
import "dart:async";
Stream<int> fibo() async* {
int lastBut1 = 0;
int last = 1;
int ret;
yield lastBut1;
yield last;
while(true){
ret = lastBut1 + last;
lastBut1 = last;
last = ret;
yield ret;
}
}
ポイントはimport "dart:async";
、Stream
、async*
とyield
です。
コールサイトStream
async for
...in
版
次にGenerator関数を呼び出す側です。
まずはシンプルにStream
ならではのawait for
...in
版です。
const COUNT = 10;
main() async {
int i = 0;
await for(int f in fibo()){
print("fibo($i) = $f");
i++;
if(i >= COUNT) break;
}
}
せっかくのfor
...in
ですが、無限数列なのでループカウンタを導入して適当なところでbreak
しています。
ちゃんとStream
として扱えました。
なお、main
はvoid
とすると怒られます。
Future<void>
とすると、やはり怒られます。( ´∀`)
Future
やFuture<dynamic>
とすれば良いのですが、ここでは省略して暗黙的にdynamic
にしています。
StreamIterator
版
次はStreamIterator版です。
終了条件にループカウンタが必要なので、これはこれで素直かもしれません。
StreamIterator itr = new StreamIterator(fibo());
for(int i = 0; await itr.moveNext() && i < COUNT ; i++){
print("fibo($i) = ${itr.current}");
}
DartにはC言語でいうところのカンマ演算子が無いので、StreamIterator
の初期化はfor
文の外で行っています。
また、Stream
からgetterでStreamIterator
が取り出せないので、Stream
を引数にStreamIterator
をコンストラクトしています。
他方で、moveNext()
はfalse
返したらそれがループ終了条件なので、お作法としてfor
文の中、継続条件のところで実行しています。
今回もmoveNext()
は永久にtrue
ですけどね。
Stream
関数パイプライン版
次はStream
に戻って、関数パイプラインで実現してみます。
無限数列なのでなんとかして止めなければいけません。
takeWhile()
というフィルタ関数でStreamIterable
を切り詰めると良いようです。
int i = 0;
fibo().takeWhile((_) => i < COUNT).forEach((f){
print("fibo($i) = $f");
i++;
});
DartはクロージャをサポートするのでtakeWhile()
、forEach()
内でもループカウンタが参照できてます。
Stream
関数パイプライン版2
何番目のフィボナッチ数かを表示しなくて良いならば、ループカウンタを省いてずっと簡単に書けます。
fibo().take(COUNT).forEach((f) => print("fibo = $f"));
実は、というか当然ながら、take()
内部にはカウンタがあります。
それでも、アプリ側で気にする必要がないのが利点です。
感想
やっぱ速いですね。
Try Dart!の再帰版フィボナッチ数計算プログラムと比べると圧倒的に速いです(体感ですが)。
もっとも、Try Dart!はO(N²)、本プログラムはO(N)なので比較するのも何ですが。
同期版と比べても、全く遜色が無いスピードで実行されました。
また、fibo()
が本質的にループする処理でありながら、ループ終了条件や、得られた値をループ内で利用する処理(今回はprint()
のみ)を分離して、純粋にフィボナッチ数列を求める処理として実装できるのは、やはり素晴らしい。
おわりに
これでStream
のプログラミングがコールバックのネストやチェーン無しに、同期処理的に記述できるようになりました。
DartのGeneratorでフィボナッチ数列 (同期版)と比較してもらえれば分かりますが、コードはほとんど同じです。
ライバル言語よりちょっと早い実装ですね。