Archive - 2010.12
2010年を振り返る
- Misc - 10.12/31
今年もありがとうございました。無事、更新を続けられました。一応毎年、31日は一年間を振り返っていたようなので、今年も振り返ることにします。
まず言えるのはもはやゲーマーじゃねえ、ということのようで、悲しいことに。ゲサイトじゃなくなったのはしょうがないとしても、プレイすらしなくなっていました……。今年は過去最高潮にゲームしてません。積みまくりです。こんな自分になるとは想像もつかなかったなあ。とか言いながらもこっそりGeometry Wars 2のEvolvedモードで日本3位ぐらい世界50位ぐらいなスコアに更新していたりはしました。全体的にゲームやらなかったぶん、ジオメトリだけはしっかりやったということで、ヨシとしますか。来年はもっとゲーマーでいたいですね。
なお、去年遊んだ中でのベストゲームはいりす症候群!です。10月頃に初めて知ったのですが、もう激ハマリ。フリーゲームですし、最高に面白いので皆様も是非。ゲームは勿論、ストーリー/演出面でも味わい深かった。大変素晴らしい。来年のゲームはPortal2に大期待、かな。そういえば、年末のついこないだに配信されたXbox LIVE インディーズ ゲームのREVOLVER360は、大変美しい映像と素晴らしいプレイ感覚で非常に面白いので現在プレイ中。
さて、ゲーム以外ではどんな一年だったかというと、キャッキャウフフ。過去の自分からじゃ全く考えられないぐらいキャッキャウフフした一年でした。そんなに沢山というわけじゃないですがC#系の会に出たりしました。そうやって実際に人と顔を合わせることでTwitter上でのコミュニケーションの輪も広がる、という効果でか、Twitterでの@率も高くなっていきました。おお、キャッキャウフフ。と、いうとアレですが、悪いことではないですよね。人付き合いは相当に苦手なのですが、幾分か何とかなってきたようにも思えます。いや、まだ全然ですが。徐々に徐々に。
C#erとしての成長度は、うーん。ちょっと鈍いかなあ。さすがに、初めてC#に触れ本格的にプログラミングを始めましたな2008年、ようやくプログラミングが分かってLinq最高ーとどんどん知識を吸収出来た2009年、に比べて鈍化してしまうのもしょうがないのかなー、とは思いつつも、その原因ってジャンルを広げられなかったからなのではないかなあ、と思います。結局今年もLinqですからね。来年もLinqな気がしますが。今年の成果物はlinq.jsをガッと書き換えたりDynamicJson作ったり、そこそこなくはないけれど、しかし1年分の仕事じゃあないのですよねえ。
来年
継続中の積みタスクの消化がー。DynamicJsonの更新とかReactiveOAuthの更新とかlinq.jsの更新とか。やるべきことは決まっているし、別にやり始めればそんなに時間のかかるものでもないので、とっとと手をつけて終わらせるべきなのである。と、11月頃から思っていたはずが手付かずでここまで来てしまった……。ということは、来年も永遠とこのままではフラグ。
何か作ると言い続けてやるやる詐欺が続いているのですが、手元にWindows Phone 7があるのが好機なので、それでSilverlightの学習と何か作るの達成を。と思ってますです。クライアントサイドでは。サーバーサイドは、ねえ。C#から離れてnode.jsを触るのが良いかな、とは思っていて。もしくは、Rxチームの次のミッションはサーバーサイドでの応用だと思うので、とりあえず追っかけてればそのうち時流に乗れるのではないかという楽観論。
というわけで、来年も暫くはReactive Extensionsとその周辺、プラスアルファで何か、といった感じでしょうか。興味あるのことは沢山ありますが、色々なことに手を出せるほどにはキャパシティの限界容量があまりないわけなので、現実を見つつ、着実に成長出来れば良いかな、と。まだあわてるような時間じゃない。と、言いたいところなのですが、私もあまり若いとも言えない年齢なので(一応まだ20台ですがー)、そろそろ焦ったほうがいい感じではある。やっぱ実務的な知識があまりにも欠けてるのよねえ、とほほ。来年は、少しその辺も見据えて動かなきゃいけないかもですね。
そんな感じですが、来年もよろしくお願いします。
Linqと総当り
- C# JavaScript linq.js - 10.12/28
各所でAdvent Calendarも終了し皆様お疲れさまでした。自分のよく知っている言語はもちろんですが、他の言語を見ていても非常に楽しめて良いですね、特に普段自分の書かない言語で、入門からちょっと突っ込んだものまでまとめて見れるというのは非常に良い機会でした。
そんな中で見かけたScalaによる リストモナドを使ってみる - みずぴー日記 という記事。おお、スッキリかっこよく求まりますね、Scalaカコイイ。さて、総当り。非決定性計算。リストモナド。といったら、Linq、クエリ式。そんな風に反射的に連想が成りたつならもう立派なLinq使いですね!いやまあ、クエリ式で総当たり - NyaRuRuの日記で読んだことがあるから、というだけの話なのですがー。ほとんど直訳でいけるとあるように、Scalaで書かれたsend + more = moneyもまた、直訳で書けました。
var digits = Enumerable.Range(0, 10); var solve = from s in digits from e in digits.Except(new[] { s }) from n in digits.Except(new[] { s, e }) from d in digits.Except(new[] { s, e, n }) from m in digits.Except(new[] { s, e, n, d }) from o in digits.Except(new[] { s, e, n, d, m }) from r in digits.Except(new[] { s, e, n, d, m, o }) from y in digits.Except(new[] { s, e, n, d, m, o, r }) where s != 0 where m != 0 let send = int.Parse("" + s + e + n + d) let more = int.Parse("" + m + o + r + e) let money = int.Parse("" + m + o + n + e + y) where send + more == money select new { send, more, money }; foreach (var item in solve) Console.WriteLine(item);
ちゃんちゃん。こういうの見ると、各言語は同じとこに向かってる感がありますね。微妙に表現は違いますが、同じ発想が通じるし同じように書ける。その点はC#もScalaもF#も同じパラダイム、同じ未来を目指して向かってるのではないかと思います。Javaは微妙に落伍している気がしますが、もう少し何とかなって欲しいものです。
と、いうだけなのもアレなので、 int.Parse(”" + s + e + n + d) の部分について。数字を文字列的な並びとして解釈したい、というわけですが、手段をまとめるとこんな感じになるかしらん。
// という数字があった時に123にしたい var x = 1; var y = 2; var z = 3; // ダルい var xyz1 = int.Parse(x.ToString() + y.ToString() + z.ToString()); // 複数ある時はこちらで var xyz2 = int.Parse(string.Concat(x, y, z)); // コンパイル後の結果はxyz2と一緒 var xyz3 = int.Parse("" + x + y + z); // 文字列変換しない var xyz4 = x * 100 + y * 10 + z; // ↑のは桁数が多い時に泣くのでこうしてやる var xyz5 = new[] { x, y, z }.Aggregate((a, b) => a * 10 + b);
文字列にして並べてintに変換するのがお手軽なわけですが、.ToString()を並べていくのはダルいわけですよね!というわけで、そんな時はstring.Concatを使うと全部まとめて結合出来ます。しかし int.Parse(string.Concat と並ぶのすらダルい、とかいう不届き者は先頭に”"と足し合わせることでstring.Concatで結合したのと同じ結果を得られます。これは、演算子は左から適用されていきますが、文字列と数値を+で足すと文字列として足される、以下繰り返し。の結果。キモチワルイといえばキモチワルイので、積極的に使うかというと悩ましいところですが……。
そもそも数字を扱うのに文字列に変えてー、とかが邪道だという話も割とある。効率的な意味でも。なので、そういうときは2の桁は10倍、3の桁は100倍……。とかやっていると桁数が多いときはどうするのどうもしないの?という話なので、Aggregateを使うという手もあります。Aggregateは一般的な関数型言語でいうfoldlに相当。左からの畳み込み演算。ところで、では右からの畳み込みはどうすればいいの?つまりはfoldrはどうなのか、というと、これは.Reverse().Aggregate() のようで。右からなら逆にすればいいぢゃない。
ところで、C#のLinqで出来ることはJavaScriptのlinq.js - LINQ for JavaScriptでも出来ますよ?やってみます?
var digits = Enumerable.Range(0, 10); var solve = digits .SelectMany(function(s){ return digits.Except([s]) .SelectMany(function(e){ return digits.Except([s, e]) .SelectMany(function(n){ return digits.Except([s, e, n]) .SelectMany(function(d){ return digits.Except([s, e, n, d]) .SelectMany(function(m){ return digits.Except([s, e, n, d, m]) .SelectMany(function(o){ return digits.Except([s, e, n, d, m, o]) .SelectMany(function(r){ return digits.Except([s, e, n, d, m, o, r]) .Select(function (y) { return { s: s, e: e, n: n, d: d, m: m, o: o, r: r, y: y} })})})})})})})}) .Where("$.s != 0") .Where("$.m != 0") .Select(function(x){ return { send: parseInt("" + x.s + x.e + x.n + x.d), more: parseInt("" + x.m + x.o + x.r + x.e), money: parseInt("" + x.m + x.o + x.n + x.e + x.y)}}) .Where(function (x) { return x.send + x.more === x.money }); solve.ForEach(function (x) { document.writeln(x.send + ":" + x.more + ":" + x.money) });
C#クエリ式からの変換のポイントは、from連鎖をSelectManyの連鎖で、但しカッコは閉じず変数のキャプチャを内包したままで最後にSelectで一旦整形してやるというところです。正確なクエリ式の再現とはなりませんが、この程度ならば、まあ何とか書けなくもないレベルとなります(正確なクエリ式の変形結果の再現をやると手では到底書けないものになる)。
ちなみに総当りなので結構時間がかかってIEだと泣きます。Chromeなら、まあそれなりな速度で求まるかなー。
Rx Christmas Release 2010によるPublishの変化解説
メリークルシミマス。Happyなことに、Reactive Extensions for .NET (Rx)が更新されました。なんと、WP7に標準搭載されたことだし安定性・互換性はある程度保証されてきたよねー、とか思った側から完全に互換を崩壊させる素晴らしいBreaking Changesをかましてきました!常識では計り知れない大胆な所業。そこにしびれるあこがれれぅ。
さて、今回の変化はJoin/GroupJoin/Windowの新規搭載とPublish系の変更です。Joinについてはまた後日ということで、今回はPublishの変更について解説します。で、ですねえ、これはもう100%変わってます。昨日ちょうどPublishによる分配、とか書いたわけですが、出したコードは全部動かないです!素晴らしいタイミング!Ouch!
端的に言えばPublishは引数にISubjectを受け取るよう変化。Prune/ReplayはPublishに統合されたことにより消滅。何でかというと、元々、分配ソースにSubjectを使うのがPublish、BehaviorSubjectを使うのが初期値付きPublish、AsyncSubjectを使うのがPrune、ReplaySubjectを使うのがReplayでした。分配ソースを任意で受け取るようになったため、メソッドが分かれる意味がなくなったというわけですね。
ナンタラSubjectの解説は、まあまた後日にでも。簡単に言えばSubjectは普通のイベントと同じで素直な挙動、つまりOnNextしたら値が流れる。AsyncSubjectは非同期を模していて、OnNextされたら値を一つだけキャッシュし、OnCompletedされたらキャッシュした値を流す。以降はSubscribeされるたびにキャッシュした値を即座に流し、OnCompletedも発行。ReplaySubjectはOnNextされる度に値をキャッシュし、それを流す。Subscribeされるとキャッシュした値を全て即座に流す。BehaviorSubjectはOnNextされる度に最新の値一つのみをキャッシュし、それを流す。Subscribeされるとキャッシュした値を即座に流す。
イマイチ分かりづらいですね:) というわけで本当に詳しいことは後日で。AsyncSubjectだけは、今までにも、そういう挙動であることの意味とかしつこく書いてきましたが。
そして、IConnectableObservableが戻り値となるものはMulticastというメソッド名になりました。引数は勿論、ISubjectを受け取るという形に。では、昨日のコードで例を。新旧比較ということで。
Observable.Range(0, 10) .Select(_ => int.Parse(Console.ReadLine())) .Publish(new Subject<int>(), xs => xs.Min().Zip(xs.Max(), (min, max) => new { min, max })) .Subscribe(Console.WriteLine);
Publishの第一引数にSubjectを突っ込みました。第二引数にはそれで分配されたIObservableが渡ってくるという塩梅です。
var input = Observable.Range(0, 10) .Select(_ => int.Parse(Console.ReadLine())) .Multicast(new Subject<int>()); input.Min().Zip(input.Max(), (min, max) => new { min, max }) .Subscribe(Console.WriteLine); input.Connect();
こちらがMulticastです。まあ、普通にISubjectを受け取るようになったというだけで、今までのPublishでIConnectableObservableが返ってくるのと同じです。さて、ISubjectを受け取るということは、外部のISubjectを渡してもOKです。新しくこんな分配が可能になりました。
var input = new ReplaySubject<int>(); Observable.Range(0, 10) .Select(_ => int.Parse(Console.ReadLine())) .Publish(input) // 流れてくる値をReplaySubjectに保存する .Max() .Subscribe(i => Console.WriteLine("Max:" + i)); var inputArray = input.ToEnumerable().ToArray(); // 入力値を配列に変換
といっても、この例は全く意味がなくて、Max()で止めておいて、その戻り値をinputとして受ければいいだけの話なのですが。上手い利用例が浮かばなかったのです!まあ、色々と応用しどころというのは生まれてくるのではないかと思います。
まとめ
この破壊的変更ですが、私としては好意的に捉えたいです。如何せんPruneというメソッド名は意味不明でしたし、オーバーロードが8つもあるという状況も良くなかった。今回整理されたことで、使いやすくなったと思います。が、しかし、WP7版との互換性が切れてしまったのは相当痛い。今までusingをプリプロセッサディレクティブで#if WINDOWS_PHONE using Microsoft.Phone.Reactive という感じに切り替えてWPF/SL/WP7で互換を取っていたのですが、ここまで派手に互換性なくなるとなあ。
ちなみにWP7標準搭載”ではない”DevLabs版のRx for WP7というのも用意されているので、そちらを使えばいいわけなのですが、それはそれでどうかなー、どうかなー、困った話で。
それと、この変更はまだ追随されていませんが、そのうち System.Interactive(Ix.NET/EnumerableEx) や RxJS にも派生してくるような気がするので、再びAPI安定していない状態に戻った感がありますねー。要チェックで要注意で。Rxチームは大変なクリスマスプレゼントを贈ってきました。そして、Rxが.NET 4 SP1に入るの?入らないの?的な希望観測もあったわけですが、私個人の印象としては、SP1入りな可能性はなくなったな、という気がしてます。まだまだ作り替える気満々だもの、これ。
Reactive ExtensionsとPublishによるシーケンスの分配
Twitter上で見たお題、10個コンソールから整数の入力を受けて最少と最大を出す。Linqで書くならこうでしょうか。通常は0-10でfor文を回すところはRangeで。あとはMinとMax。int.Parseなので数字以外は例外飛んでしまいますが、その辺は無視で。
var input = Enumerable.Range(0, 10) .Select(_ => int.Parse(Console.ReadLine())); var max = input.Max(); var min = input.Min();
はい。これだと20回入力させることになってしまいます。Maxで列挙され、Minでも列挙され。ダメですね。というわけで、配列に一度保存しますか。
var input = Enumerable.Range(0, 10) .Select(_ => int.Parse(Console.ReadLine())) .ToArray(); var max = input.Max(); var min = input.Min();
よろしい。しかしこれの列挙回数は3回です。ToArrayで一度、Maxで一度、Minで一度。しかもMaxとMinを出したいだけなのに、わざわざ配列に保存してしまっています。もし配列が巨大になる場合はメモリ的な意味でもちょっと嫌ですねえ。さて、そこで出番なのが、プッシュ型シーケンスを持つReactive Extensions。プッシュ型ならば分配が可能です。やってみましょう?
Observable.Range(0, 10) .Select(_ => int.Parse(Console.ReadLine())) .Publish(xs => xs.Min().Zip(xs.Max(), (min, max) => new { min, max })) .Subscribe(Console.WriteLine);
ふむ、これはクールだ……。Rxの格好良さは異常。
Publishは今までにも何度も出してきましたが、シーケンスを分配するメソッドです。今までは引数なしにして、IConnectableObservableを変数に受けて、それに対してポコポコ足して、最後にConnect。としていましたが、Publishには引数有り版も用意されています。引数有りの場合は分配されたIObservableが変数として渡ってくるわけです。つまり上のコードは
var input = Observable.Range(0, 10) .Select(_ => int.Parse(Console.ReadLine())) .Publish(); input.Min().Zip(input.Max(), (min, max) => new { min, max }) .Subscribe(Console.WriteLine); input.Connect();
と等しいということになります。ちなみにPublish内でSubscribeしたって構わないわけなので
Observable.Range(0, 10) .Select(_ => int.Parse(Console.ReadLine())) .Publish(xs => { xs.Min().Subscribe(x => Console.WriteLine("min:" + x)); xs.Max().Subscribe(x => Console.WriteLine("max:" + x)); xs.Sum().Subscribe(x => Console.WriteLine("sum:" + x)); xs.Average().Subscribe(x => Console.WriteLine("avg:" + x)); return xs; }) .Subscribe();
といったような書き方も可能ですね。では、min,max,sum,avgと4つを返したい場合はどうしよう。Join-And-Thenを使います。
Observable.Range(0, 10) .Select(_ => int.Parse(Console.ReadLine())) .Publish(xs => Observable.Join(xs .Min().And(xs.Max()).And(xs.Sum()).And(xs.Average()) .Then((min, max, sum, avg) => new { min, max, sum, avg }))) .Subscribe(Console.WriteLine);
この例だと単純に直列に繋いでいるだけなので、Zipと同じようにAndで繋いでThen。というだけですね。ちょいとゴチャッとしていますが。Joinの強力なところはAnd-Thenで作るPlan(Thenの戻り値)を複数受けられるという点にあります。これにより複雑な結合を生成する事ができます。ZipとMergeが混ざったような、でももう少し複雑な何か。といった雰囲気。
Publish
Publishはオーバーロードが8つもあって最初戸惑ってしまいますが、分類としては二つに分けられます(そしてそれぞれFuncを受け取る/スケジューラを受け取るの組み合わせで4つに分かれて2×4=8のオーバーロード)。一つは引数として初期値を受け取らないもの。もう一つは受け取るもの。今までは全部、受け取らないものを見てきました。内部的にはSubjectを使って分配しています。一方、初期値を受け取るものは内部的にBehaviorSubjectを使って分配します。というわけで、どんな挙動を取るのかよくわからない、という場合はBehaviorSubjectを試してみると良いでしょう!(何だこの解説する気ゼロな投げっぱなしな説明は)
ところでな余談
注意しないといけないのは、ふつーのシーケンスがあって、それをRxで分配やるとはっきし言って遅いです。列挙数が多くなると、ただ配列舐めるのに比べて数万倍のパフォーマンス差が出たりします。ほとんどのシチュエーションでRxで分配するぐらいなら二度三度舐めたほうが速いです。まあ、分配に限らずRxが普通に遅いということなのですがー。ご利用は計画的に。今回の例のConsole.ReadLineで、というのは、当然Rxのほうが速いですが。入力とリアルタイムに応答していますからね。Reactive!ようするところ、Rxが活きるのってそういうところ、かしらん。
linq.js & Reactive Extensions for JavaScript(RxJS)入門
- JavaScript Rx linq.js - 10.12/20
このエントリはJavaScript Advent Calendar 2010 : ATNDの20日目として書きます。一つ前はsecondlifeさんのコマンドラインから JavaScript のシンタックスチェックを行う方法 - って、なんでですか〜 - subtechでした。
普段はC#でもしゃもしゃしている、@neuecc(twitter)といいます。そんなわけで今回はC#畑からのJavaScriptライブラリを二つほど紹介します。
ここではC#の中でも、LINQ: .NET 統合言語クエリという機能から来ているlinq.jsとRxJSを紹介します。linq.jsはコレクション操作を、RxJSはイベント操作と非同期操作に関するライブラリとなっています。どちらもwindowオブジェクトには一切触らない、DOMやXmlHttpRequestとは全く無関係の中立で地味な、phpspotで紹介されそうもない、それだけで何が出来るかというと別に何もできないけれど、あると便利。また、Enumerable(linq.js)とObservable(RxJS)という枠組みが双対であるという、面白い影響の与え方をしているため、セットで考えるとより深イイ話になるから、ちょっと長くなりますが二つ一緒に紹介します。
linq.js
linq.jsはLINQのうち、Linq to ObjectsというものをJavaScriptに移植したものとなります。ダウンロードは下記URLから。ライセンスはMs-PL、というと聞きなれないかもしれませんが、MITライセンスに近い、かなり緩めのものとなっています。
公式で移植されたものではなく、野良で勝手にやっているだけの話であり、作者は、私です。ん……。あ……。……。さて、LINQとは何ぞや、統合言語クエリがうんちゃらかんちゃら、SQLがどうのこうの、というのはJS的には全く関係ないのでスルーで!Linq to Objectsに絞って何ぞやというと、「関数型っぽいコレクション処理ライブラリ」です。ひとまず簡単な例から。
// こんなテキトーな配列があったとして var array = [101, 20, 2, 42, 33, 47, 52]; // 偶数だけ二倍して出力 : 40, 4, 84, 104 Enumerable.From(array) .Where(function(x){ return x % 2 == 0 }) .Select(function(x){ return x * 2 }) .ForEach(function(x){ document.writeln(x) });
よくある関数を渡してコレクション処理するスタイルです。グローバルに配置される名前空間はEnumerable。そこから、既存の配列に対して適用する場合はFromで包んで(jQueryの$のようなものと考えればOK、数字列の場合はRangeなどもあります)、以降はLinqの専用メソッドをチェーンで繋いでいく、というスタイルを取ります。ちなみにEnumerableはイニュミラボーと読むようです。最後のボーは、私は日本人なのでブルって言いますが。イニュミラブル。
ところでfilterしてmapしてforeach。そうですFirefoxにあるArrayへのmap/filterです。Whereがfilter、Selectがmap。名前はSQL風味ですが、つまるところ単純な話そういうことです。フィルタぐらいならjQueryにもmap/grep/eachがあるね。他に目を向ければ、コレクション系専用ライブラリではUnderscore.jsが有名のようです。
それらとの違いですが、一つはfunction(){}という、冗長にすぎる関数記述を省く文字列による簡易記法の搭載。もう一つは完全な遅延評価の実現。そのため、適用する関数を自由に追加していくことができます。簡単な例をもう少し。
var array = [101, 20, 2, 42, 33, 47, 52]; // この時点ではまだ列挙されていない。 // $は引数を示すプレースホルダーで、この場合 function(x){ return x%2==0 } と同じ var query = Enumerable.From(array).Where("$%2==0"); // 更に二乗したうえで配列へ変換するならToArray var array2 = query.Select("$*$").ToArray(); // [400, 4, 1764, 2704] // 昇順に並び替えた上でセパレータをつけて文字列化 var orderedStr = query.OrderBy().ToString(":"); // "2:20:42:52" // 先頭二つのみを列挙 query.Take(2).ForEach("alert($)"); // 20, 2
と、いったように様々なメソッドを繋げてコレクションを変形し、最後に任意の形に変換するというのが基本的な利用法になるわけです。わからん。というわけで図にするとこんな感じです。
構図的にはjQueryと同じで、Enumerable.From/Range/etc.. によってEnumerableオブジェクトが生成され、その中でのメソッド(Select/Where/etc..)はEnumerableオブジェクトを返す。そのため幾らでもメソッドチェーンを繋げられる。jQueryと違うのは、繋げた関数が実行されるのは、戻り値がEnumerable以外になるときまで遅延される。Enumerable以外を返すメソッドとは、ToArray(配列に変換)、ToString(文字列に変換)、ToObject(オブジェクトに変換)、Contains(値が含まれていればtrue、含まれていなければfalse)、Max(最大値を返す)、ForEach(列挙する)、etc…(まだまだ大量にあります)。
遅延評価であることによるメリットとして、無限リストを扱えることが挙げられます。というわけで、無限リストを活用する例を一つ。「nを1から初めてその2乗を足していき、和が2000を初めて超えたとき和はいくつになるかという問題」をScalaで解いてみたから、nを1から(以下略)をlinq.jsで解いてみます。
var result = Enumerable.ToInfinity(1) // 1から無限大まで数値をジェネレート [1, 2, 3, 4,...] .Select("$*$") // 二乗 [1, 4, 9, 16,...] .Scan("$+$$") // 和 [1,5,14,30,...] .First("$>2000"); // 2000を超えた最初の要素
元がC#/Linq版のコードであるから当然ではありますが、リンク先のScala版のコードと完全に一致、ですね。JavaScriptは関数型言語。無限リストの生成には、今回はToInifinityを使いましたが、関数型言語に馴染みのある人ならばUnfoldなどもありますので、望む物が生成出来るはずです。
“$+$$”でサクッと和を出せるのは中々強力で便利。やりすぎるとイミフになるので、こういうササッとした部分にだけ限定すれば。任意の識別子を使いたい場合は”sum, x => sum + x”というように=>の左側は引数、右側は式、という形でも書けます。なお、実装は new Function(”$,$$,$$$,$$$$”, “return ” + expression) というだけです。渡す文字列が式でないとダメな非常に単純な理由。
その他、メソッド類の一覧と、その場で実行/確認可能なLINQ Padはlinq.js Referenceで。
といったように、リアルタイムに実行結果を確認しながら試せます。無限リストを停止条件つけないで書いてしまっても、列挙は1000件までにリミッターかかるので一安心。
メソッド群ですが、LINQにある標準クエリ演算子(と、言うと大仰ですが、ようするにただのメソッドです)を全て実装、その上でHaskellやRubyなどのコレクション用メソッドを眺めて、便利そうなら移植してあります。そのため、コレクションライブラリとしてはとしてこのメソッドが不足してる!と不満を感じることはないはず。また、遅延評価を活かしてメソッドを組み合わせることにより、大抵の操作が可能になっています。
とはいえ、そのせいで肥大しすぎな感がなきにしもあらず。とりあえず、フィルタ(Where)とマップ(Select)、非破壊的で複数キー連結が可能なソート(OrderBy/ThenBy)、重複した値を取り除く(Distinct)といった辺りだけ押さえておけば良いかなー、と。
そういえばでドットが前置なのは何で?というと、その方が入力補完に便利だからです。
この辺の話はJavaScriptエディタとしてのVisual Studioの使い方入門で。IDEを使ってJSを書くなら、ドットは前置一択になります。補完のない普通のエディタで書くのならスタイルはお好みで。私としては、入力補完・コードフォーマッタ・デバッガとのシームレスな融合などなどから、JavaScriptであってもIDEを使うのが良いと思ってます。
Reactive Extensions for JavaScript
続けてReactive Extensions for JavaScript(RxJS)について。Linqという名前は付いていませんがLinqの一味と見なせます。いわばLinq to Events, Linq to Asynchronous。ということで、対象となるのはイベントと非同期。ダウンロードは下記URLの右側、Rx for JavaScriptから。
Reactive Extensions for .NET (Rx)
こちらはlinq.jsと違って公式が提供しています。
さて、ではこれは何が出来るのでしょう。出来るのは(Functional) Reactive Programmingです。とは何ぞや。というと、既に親切な解説があるので やさしいFunctional reactive programming(概要編) - maoeのブログ そちらを見るといいと思うな!
とりあえず簡単な例をまず先に。
// マウスの動きの座標ストリーム(無限リスト) var mousemove = $("#js_advcal_field").toObservable("mousemove") .Select(function (e) { return { X: e.pageX, Y: e.pageY} }); // 位置をTextに書き出し mousemove.Subscribe(function (p) { $("#js_advcal_status").text("X=" + p.X + ":Y=" + p.Y) }); // 1.5秒遅れて四角形を座標位置に出す mousemove.Delay(1500) .Subscribe(function (p) { $("#js_advcal_rect").css({ left: p.X, top: p.Y }) });
コード似てませんか?今まで出してきたLinq to Objectsのコードに。RxJSは、イベントをコレクションとして扱うことでフィルタ(Where)やマップ(Select)を可能にします。図にするとこうです。
toObservableすることにより、jQueryでアタッチされるMouseMoveイベントは、時間軸上に無限に発生し続ける「無限リスト:Observable Collections」として変換されます。このコレクションの上では時間軸を自由に扱えるため(そもそもMouseMove自体が、いつ次の値が発生するか不確定な時間の上にのっている)、ごくごく自然に、1.5秒後(Delay)という指示を与えるだけで到達時間を遅らせることが出来ます。
RxJSもlinq.jsと同じく、基本的にメソッドの戻り値はObservableオブジェクトとなっていてひたすらメソッドチェーンしていきます。違うのは、linq.jsはToArrayやToString、ForEachなどなど、Enumerable外に出るメソッドが複数ありますが、Observableの場合はSubscribeのみです。SubscribeメソッドがForEachのような役割を担っています。何でToArray出来ないの!というと理由は簡単で、扱う対象が時間軸上に流れる無限リストだからです。無限を有限のArrayに変換は出来ませんよねー。本質的にObservable Collectionsは非同期な状態なので、何か戻り値を返すということは出来ません。出来るのは、向こうからやってくる値に対して実行する処理を示すことだけです。
RxJSは、扱いにくい時間や非同期、複数のイベントが同時に絡む時のイベントの合成を、慣れ親しんだシンプルなコレクション処理のように見た目上落としこんで、foreachするように処理を適用することが出来ます。それが特徴となります。
jQuery
前のコードに出ていた$(”#hoge”)はjQueryです。脈絡なく出してきているわけですね!解説が前後してれぅー。どういうことなのかというと、RxJSは基本的にwindowとは中立です、が、メインで扱う物はDOM(イベント)だったりXmlHttpRequest(非同期)で、これらは抽出の必要があったりクロスブラウザの必要があったりと、一手間二手間な問題を抱えている。それを解決してくれるのがjQueryだったり他のライブラリだったりするわけですね。そこで取られた手段が、jQueryに乗っかること。ようするにプラグイン。RxJSとjQueryを繋ぐ部分を注入。そうして注入されたのがtoObservableというわけです。
linq.jsもjQueryもRxJSも、基本的にメソッドチェーンで自分の世界に閉じっぱなしです。jQueryはモナドだという記事がありましたが、linq.jsもモナドです。RxJSもモナドです。いや、本当に。ただそのへんの理屈は割とどうでもいいわけですが、ただ、交互に変換出来ると便利よねー、なところはあるわけで、三者はプラグインを介して上記の図のような形で遷移可能になっています。
Asynchronous
最後に非同期を。非同期もObservable Collectionsになる、というわけで例から行きましょう。
$("#js_advcal_twbutton").toObservable("click") .Do(function () { $("#js_advcal_twitter").empty() }) .SelectMany(function () { return $.ajaxAsObservable({ url: "http://twitter.com/statuses/public_timeline.json", dataType: "jsonp" }) }) .SelectMany(function (json) { return Rx.Observable.FromArray(json.data) }) .Where(function (status) { return status.user.lang == "ja" }) .Select(function (status) { return $("<p>").text(status.user.screen_name + ":" + status.text) }) .Subscribe(function (q) { $("#js_advcal_twitter").append(q); });
胡散臭い(笑)public_timelineボタンをクリックすると、Twitterのpublic_timelineから日本人のツイート(user.lang==”ja”)のみを表示します。これは、ちょっとメソッドチェーンが多めで微妙にワケワカラン。そんな困ったときはとりあえず図。
起点はボタンのクリックです。これもまたMouseMoveの時と同じで考え方としては無限リスト状。一回目のクリック、二回目のクリック、と無限に続いていきます。このコレクションに対する処理として、次に流れてくるのはDo。これは副作用で、コレクションの値以外の、流れてきたということをトリガーにして外部に影響を与えたい時に使います。今回はクリックをトリガーとして、一旦DIVの中の要素をクリア(empty())しています。
そしてSelectMany。SelectManyはSelectと似ていますが、1:多(だからMany)へと分配するのが特徴です。ここでコレクションの流れは非同期へとバトンタッチされます。非同期のリクエスト、今回はjQueryにおんぶに抱っこでajaxAsObservableにより、twitterのpublic_timeline.jsonからデータを取得。特徴的なのは、非同期なので戻り値が得られるまでに若干のタイムラグがあるわけですが、それは以前に扱ったDelayと同じように、時間軸が少し右に移るだけで、流れ自体はそのままで扱えてしまいます。
1:多ですが、非同期リクエストの戻り値は1なので、見た目上は一個の値が変形しているだけのように見えて、再び次のSelectMany。ここでの値はJSONで、20個分のpublic_timelineのstatusが届いています。それを1:他という形でバラす。RxJS上では「イベント」「非同期」を載せてきましたが、「配列」も問題なく載っかるということです。
ここまで来たら、あとは普通のコレクション処理と同じようにWhereでフィルタリングし(言語が”ja”のみを通す)、Selectで変形し(jQueryで新しいDOMノードを作成)、Subscribeで実行処理を書く(divの中にappend)。
というわけで、「イベント」「非同期」「配列」を一本のコレクションへと合成し、統合しました。一見すると、ただそれだけのことに何でわざわざ複雑めいたことを、と思えてしまいますが、複数の非同期を合成したくなったら?待ち合せたくなったら?などなど、シチュエーションが複雑になればなるほどに、威力を発揮します。
つまりそれJSDeferredで…
です。領域はかなり被ると思います。waitはDelay、nextはSelectまたはSelectManyが相当するでしょう。
// Hello -> 5秒後 -> HelloWorld のalertが出るというもの(UIはフリーズしません) Rx.Observable.Return("Hello") // パイプラインに"Hello"を流し始める .Do(function (x) { alert(x) }) // alertを出す .Delay(5000) // 5秒遅延 .Select(function (x) { return x + "World" }) // 値にWorldを結合 .Subscribe(function (x) { alert(x) }); // パイプラインの実行開始+alertを出す
例はJSDeferred 紹介より、少し違いますが。また、関数のDeferred化のdeferred.call/failはAsyncSubjectのOnNext/OnErrorが相当しそうです。詳しい話はまたそのうち、もしくはC#で良ければReactive Extensionsの非同期周りの解説と自前実装などを。
まとめ
なげー。スミマセンスミマセン。Enumerable -> コレクション処理 -> 無限リスト -> Observable -> イベントが無限リスト -> 時間軸が不定 -> 非同期 -> コレクション処理。という流れのつもりでした!分量的にEnumerableかObservableか、どっちかに絞るべきでしたね……。もっとあっさり終えるはずだったのにどうしてこうなった。
prototype.jsはRubyっぽい色がある。Firefoxで拡張が続いてるJavaScriptはPythonからの影響が濃ゆい感じ。linq.js/RxJSは勿論C#からで、更にLinqの元はSQLは勿論なのですが、Haskellからの影響も濃く(Linqや、そして現在はRxの開発チームを率いているErik MeijerはHaskellの人で、The Haskell 98 Language Reportにも名前を連ねている)、そうこうして他言語同士が相互に影響を与えてより良くなる。というのはイイ話だなー、って思っていまして。
そして、他言語の文化を受け入れられる懐の広さと、それでもなお自分の色を持ち続けられるJavaScriptってイイ言語だよね、と思います。
と、駄エントリを〆て次のAdvent Calendarにタッチ!
Titanium Mobile + Visual Studio用のAPI入力補完vsdoc自動生成T4 Temlate
- C# JavaScript - 10.12/14
Titanium Mobileをご存知ですか?私はつい最近知ったばかりです。かなりHotみたいで紹介をよく見るし、はてブでも色々な記事がブクマ数百行ってたりしますが、さっぱり意識していなかったせいで右から左に流れていくだけで名前を覚えていませんでした。が、 【就職先決定】Titanium MobileのAppceleratorに勤めることになりました - @masuidrive blog を見て、ようやくTitanium Mobileの名前と出来ることが一致しました。つまりはJavaScriptでNativeなAndroid/iPhoneアプリケーションが作れて、とってもHotなテクノロジだそうですはい。
ちょっと触った感じコンパイルというかコード変換というか、な部分だけが提供されてるような雰囲気でIDEとかそーいうのは今のところないようで?かしらん。デバッガとかないのかな、printオンリーかしら……。ともあれ、Javascriptということで何で開発してもいいわけですね。エディタですか?ありえない!入力補完のない環境で開発なんて出来ません。デバッガがなくても生きていけますが入力補完はないと生きていけません。
というわけでVisual Studioです。以前にJavaScriptエディタとしてのVisual Studioの使い方入門という記事を書いたように、Visual StudioはJavaScriptエディタとしても最高なのです。さて、では入力補完だ。というわけで捜すとあった。Appcelerator Titanium vsdoc for Visual Studio。へー、確かに動いてるね、簡単だね。以上。
と、まあ興味持った当時はそれで一旦終えたのですが、昨日に【追記あり】「Titanium Mobile1.4」の入力支援をMicrosoft Visual Web Developer 2010 Expressで行う。 - 葛城の日記という記事を見て、そもそも元はjsonとして公式にデータが提供されていてそれを加工したものであること、またその加工っぷりはちょっといま一つであることを知りました。
さて、前置きが長くなりましたが、つまりイマイチなら自分で作ればいいんですね。幸いにもデータは公式で用意してくれているので。
T4でjsonからvsdocを生成する
いつもならドヤ顔でコード張るんですが、ちょっと長いのとC#の話なのでスルー。というわけでコードはBitbucketに置いておきますのでご自由にどうぞ。TitaniumMobileApi-vsdoc.tt。使い方は後ほど説明します。そんなの面倒臭いー、という人は生成済みのほうをどうぞ。TitaniumMobileApi-vsdoc.js。2010/12/10のver1.5.0で生成していますが、どうせVS使うならそんな手間でもないので、.ttで自分で生成するほうをお薦めします。
.ttの使い方
.tt、の前にVisual StudioでのJavaScriptの開発の仕方。ですがJavaScriptエディタとしてのVisual Studioの使い方入門で解説しているのでその通りというわけで省略。
TitaniumMobileApi-vsdoc.ttと、公式サイトにあるAPIリファレンスのJSON(api.json)を同じフォルダに入れて、TitaniumMobileApi-vsdoc.ttを開き保存(Ctrl+S)を押します。するとTitaniumMobileApi-vsdoc.jsが生成されます。オシマイ。
こんな感じに階層が出来て、8000行ほどのjsが生成されているはずです!
さて、では使ってみましょう。適当な名前のjsを作り、reference pathに先ほど生成されたjsを記述。あとはTitanium名前空間から始まるので、ポンポンと書くだけ。
解説付きで引数などなどもバッチシ!
煩わしい大量の定数も、入力補完で一覧が出るので楽ちん!
海外の、Jeremy Meltonさんの作ったvsdocとの最大の違いは、関数での生成後の戻り値のObjectもある程度補完し続けてくれることです。例えばcreateHogeとか。
これ、もとのapi.jsonでは戻り値がobjectとして記載されているため、普通に自動生成するだけだと追随出来ないのです。私はただの機械的な変換ではなく、実用に根ざしたアドホックな対応を施したため、多少は追随するようになっています。
ただし、一点だけ残念なことがあって、プロパティ名にハイフンがつくものは補完に表示されません。例えばMapViewはfont-familyといったプロパティを持つのですが補完に出てきません。これはVisual Studioの仕様というか不具合というか残念な点というか。直るといいです。要望としての報告が出てなければ報告しよう……。と思って要望を出してしまったのですが、そもそもJavaScript的にハイフンのつくのが動くほうがオカシイ!普通は動かん!ということに要望出して30秒後に気づきました。頭がホカホカだったので全然考えが回ってなかったのでする。こういうのって出した直後に頭が冷えて気づきますよね。というわけで、これはむしろTitanium MobileのAPIがオカシイのでは感。そして私は悲しいほどに赤っ恥を書いてしまった、いや、今更一つや二つ、しょうもない恥が増えてもいいんですけど、いいんですけど、恥ずかしすぎるぅ。泣きたい。
余談(T4の話)
配布用なので1ファイルにするためJsonReaderWriterFactoryを生で使って書いてる(ためちょっと冗長)のですが、そうでなければDynamicJson使ってサクッと済ませます。DynamicJsonは楽ですね、ほんと。
あと、T4で困ったのが末尾カンマ。最後の要素だけカンマ不要なわけなんですよねー、これがどう生成したものか困ってしまって。ただの文字列ならstring.Join(”,”,xs)とかで対応するわけですが、T4だとインデントとかの絡みもあるので、そういうのは使わない方が楽だし綺麗に仕上がる、けれど上手く出来ない!とりあえず今回はこんな対応をしてみました。
// こんなコードを吐く、つまり最後の要素の末尾には","は不要 { "anchorPoint": null, "animate": true, "animatedCenterPoint": null } // こんな拡張メソッド(第二引数のboolは末尾でないか末尾か) public static void Each<T>(this T[] source, Action<T, bool> action) { for (int i = 0; i < source.Length; i++) { var hasNext = (i < source.Length - 1); action(source[i], hasNext); } } // T4のView側はこんな感じで、最後に<#= hasNext ? "," : "" #>をつけて対応 <#o.Properties.Each((x, hasNext) =>{#> "<#=x.Name #>": <#=x.Value #><#= hasNext ? "," : "" #> <#});#>
微妙に苦しい感じでしたねえ。こういうのは ASP.NET/ASP.NET MVCではどう対応しているものなのかしらん。全然知らないので誰か教えてくれれば嬉しいです。
まとめ
私はこの手のダミーのvsdoc生成は色々やっていて、linq.jsのvsdocは手作業、RxJSのvsdoc作りはmono.cecilを使ってdllとXMLから結合するConsoleApplicationを作成、などなどで割と手慣れてきました。テンプレートが統合されている点と(printlnってねえ…)更新の楽さ(新しいjsonが出たらそれを放りこんで再セーブするだけ、ある意味T4はC#/VSにおけるLL的スクリプト言語と見ても良いのではないでしょーか)を考えると、T4で作るのがベストな選択に思えます。スタンドアロンで動かしたい場合は”前処理されたテキストテンプレート”も使えますしね。
さて、こうしてわざわざ手間かけて入力補完作る理由ですが、だって補完ないとやる気出ませんから!IDEで楽が出来ないなら楽が出来るよう苦労すればいいぢゃない。そしてIDEに依存するのもまた美徳です。プログラマの三大美徳。楽で何が悪いって?何も悪くないよ!あと、誰か一人が苦労すれば、お裾分け出来るわけなので、先立って作ってシェアしたいところです。
というわけで、Visual StudioでiPhone開発もAndroid開発も幸せになりましょー。個人的にはTitaniumは良いと思いますが、C#で開発できるmonotouchやmonodroidに興味津々ではありますが。んで、何で手を出し始めているかというと、linq.jsを使うシーンを求めてるんですぅー。ふふふ。というわけで、Titanium Mobileな方はlinq.jsもよろしぅお願いします。
Reactive ExtensionsとAsync CTPでの非同期のキャンセル・プログレス処理
暫くはAsync CTPを特集していく!と思っていたのですが、何だか随分と間があいてしまいました。じっくり非同期操作に必要なオペレーションは何か、と考えるに「バックグラウンドでの実行」「進捗のUI表示」「結果のUI表示」「キャンセル処理」「エラー時処理」が挙げられる気がします。というわけで、こないだまではRxで進捗表示とかエラー時処理とか見ていたわけです、決してAsync CTPをスルーしていたわけではありません!ホントダヨ?記事の分量的にどうしてもRxだけで埋まってしまったのです。
さて、ところでこれらってつまり、BackgroundWorkerですよねー。ただたんに裏で実行するだけならThreadPool.QueueUserWorkItemでいいし、結果のUIへの伝達ぐらいならDispatcher.BeginInvoke書けば…… ですが、進捗やキャンセルなどを加えていくとドロドロドロドロしてしまいます。それらが統合された上でポトペタプロパティ設定で使えるBackgroundWorkerは偉大なわけです。
では、BackgroundWorkerを使った場合とReactive Extensionsを使った場合、そしてAsync CTPのasync/await、つまりはTaskを使った場合とで比較していきます。
あ、そうそう、Async CTPの本格的な解説はmatarilloさんの訳されているEric Lippertの継続渡しスタイル(CPS)と非同期構文(async/await)やufcppさんの非同期処理 (C# によるプログラミング入門)でがっちりと解説されています。私はがっちりした記事は書けないのでひたすらゆるふわに機能を雑多につまみ食いで。あと、Reactive Extensionsとしつこく比較するのも忘れません。
BackgroundWorkerの場合
BackgroundWorkerは、DoWorkはバックグラウンドで、ProgressChangedとRunWorkerCompletedはUIスレッド上で動きます。これにより、Dispatcherだとか、そういうことを意識せずに使えます。勿論、DoWork内でDispatcher.BeginInvokeすることも可能ですが、そういう場合はBackgroundWorkerの意味があまりなくなってしまうので、設計には素直に従っておいたほうが良いです。というわけで例など。
static string HeavyHeavyHeavyMethod(string s) { Thread.Sleep(5000); // 重たい処理をするとする return s + s; } static void Main() { var bw = new BackgroundWorker { WorkerReportsProgress = true, WorkerSupportsCancellation = true }; bw.ProgressChanged += (sender, e) => { var percentage = e.ProgressPercentage; var state = e.UserState; Console.WriteLine(percentage + "%" + ":" + state); }; bw.DoWork += (sender, e) => { var worker = sender as BackgroundWorker; // くろーぢゃな場合はbwが直接取れるので不要ですが var result = (string)e.Argument; if (result == null) throw new ArgumentNullException("引数よこせゴルァ"); worker.ReportProgress(1, result); // 進捗報告 // 重たい処理が幾つかあって最終的な結果を出す // キャンセルは随時出来るようにする result = HeavyHeavyHeavyMethod(result); if (worker.CancellationPending) { e.Cancel = true; return; } worker.ReportProgress(33, result); // 進捗報告 result = HeavyHeavyHeavyMethod(result); if (worker.CancellationPending) { e.Cancel = true; return; } worker.ReportProgress(66, result); // 進捗報告 result = HeavyHeavyHeavyMethod(result); if (worker.CancellationPending) { e.Cancel = true; return; } worker.ReportProgress(100, result); // 進捗報告 e.Result = result; // 結果セットして正常完了 }; bw.RunWorkerCompleted += (sender, e) => { if (e.Cancelled) // // キャンセルした場合 { Console.WriteLine("キャンセルされたー"); } else if (e.Error != null) // 例外発生の場合 { Console.WriteLine("例外出たー"); Console.WriteLine(e.Error); } else // 正常終了の場合 { var result = e.Result; Console.WriteLine("終わった、結果:" + result); } }; // 以下実行例 bw.RunWorkerAsync("hoge"); // 非同期実行開始と初期引数 Thread.Sleep(6000); bw.CancelAsync(); // 6秒後にキャンセルするなど while (bw.IsBusy) Thread.Sleep(1000); bw.RunWorkerAsync(null); // 今度は引数なしで実行するなど while (bw.IsBusy) Thread.Sleep(1000); bw.RunWorkerAsync("hoge"); // 最後まで実行 Console.ReadLine(); }
実行結果などは他愛もないものなのでスルーで。さて、コードは見たとおりに、些か冗長なところはありますが一般的に考えられる処理は全て行えます。受け渡しがObjectなのダセーとか、EventArgsに値をセットして受け渡しダセーとか、キャンセルするのにCancellationPendingのチェックだりー、などなど思うところは色々あります。BackgroundWorkerのメリットはポトペタにあったと思われるので、時代背景的に、もうそぐわないかなあという気がかなりしています。
Reactive Extensionsの場合
Reactive Extensionsは、この手の非同期処理はお手の物。というわけでBackgroundWorkerで行った機能をまんま代替してみます。実行スレッドの切り替えはObserveOnで。
static string HeavyHeavyHeavyMethod(string s) { Thread.Sleep(5000); // 重たい処理をするとする return s + s; } // WPFで適当なリストボックス(経過表示用)と適当なキャンセルボタンがあるとする public MainWindow() { InitializeComponent(); Action<int, string> reportProgress = (i, s) => listBox1.Items.Add(i + "%:" + s); var disposable = Observable.Return("hoge", Scheduler.ThreadPool) .ObserveOnDispatcher().Do(s => reportProgress(1, s)) .ObserveOn(Scheduler.ThreadPool).Select(HeavyHeavyHeavyMethod) .ObserveOnDispatcher().Do(s => reportProgress(33, s)) .ObserveOn(Scheduler.ThreadPool).Select(HeavyHeavyHeavyMethod) .ObserveOnDispatcher().Do(s => reportProgress(66, s)) .ObserveOn(Scheduler.ThreadPool).Select(HeavyHeavyHeavyMethod) .ObserveOnDispatcher().Do(s => reportProgress(100, s)) .Subscribe( s => listBox1.Items.Add("終わった、結果:" + s), e => { listBox1.Items.Add("例外出たー"); listBox1.Items.Add(e); }, () => { }); // キャンセルボタンクリックでキャンセル CancelButton.Click += (sender, e) => { listBox1.Items.Add("キャンセルしたー"); disposable.Dispose(); }; }
んん、あれれ?進捗表示する時はDispatcherに切り替え、重い処理をする時はThreadPoolに流すよう切り替える。理屈は簡単。書くのもそのまま。しかし、しかし、これは、どう見ても非効率的。おまけにコードの見た目もUgly。ダメだこりゃ。そんな時は拡張メソッド。例えばこんなものを用意しよう。
public static class ObservableExtensions { /// <summary>Report on Dispatcher</summary> public static IObservable<T> Report<T>(this IObservable<T> source, Action<T> action) { return source.Report(action, Scheduler.Dispatcher); } /// <summary>Report on Scheduler</summary> public static IObservable<T> Report<T>(this IObservable<T> source, Action<T> action, IScheduler scheduler) { return source.Do(x => scheduler.Schedule(() => action(x))); } }
Doの変形バージョンで、actionをDispatcher.BeginInvoke(デフォルトでは。オーバーロードのISchedulerを渡すものを使えば、任意のスケジューラに変更出来ます)で行う、というものです。これなら進捗表示などにピッタリ合うはず。というわけで、適用してみます。
var disposable = Observable.Return("hoge", Scheduler.ThreadPool) .Report(s => reportProgress(1, s)) .Select(HeavyHeavyHeavyMethod) .Report(s => reportProgress(33, s)) .Select(HeavyHeavyHeavyMethod) .Report(s => reportProgress(66, s)) .Select(HeavyHeavyHeavyMethod) .Report(s => reportProgress(100, s)) .ObserveOnDispatcher() .Subscribe( s => listBox1.Items.Add("終わった、結果:" + s), e => { listBox1.Items.Add("例外出たー"); listBox1.Items.Add(e); }, () => { });
無難に仕上がりました。BackgroundWorkerと比べると、随分とすっきりします。受け渡しがオブジェクトではなく、しっかり型がついたままチェーンされること、例外処理もOnErrorの流れに沿ってすっきり記述できること、そして、何よりもキャンセル処理が楽!Disposeを呼ぶだけで、CancellationPendingのようなものをチェックする必要なくサクッとキャンセルすることが可能です。これは、処理単位が小さなメソッド毎に分割される、この場合は進捗報告を抜くとSelectの連打という形になりますが、その連打がちゃんと意味を持つわけです。
余談ですが、INotifyPropertyChanged経由のデータバインディングは自動でDispatcher経由にしてくれるようなので、その辺楽。UIパーツなんて直接触るもんじゃない、MVVM! でもObservableCollectionだとダメだったりするんですね、色々んもー。
Task(async/await)の場合
TaskにおけるキャンセルもBackgroundWorkerと同じく、キャンセル用オブジェクトの状態を確認して自分で挙動を挟む必要があります。ThrowIfCancellationRequested() を呼べばキャンセルされていた時は例外を送出して強制終了。
string HeavyHeavyHeavyMethod(string s) { Thread.Sleep(5000); // 重たい処理をするとする return s + s; } // 進捗表示用入れ物クラス class ProgressResult { public int Percentage { get; set; } public string Value { get; set; } } async void DoAsync(string start, CancellationToken token, IProgress<ProgressResult> progress) { // 進捗報告はIProgress<T>のReportを呼ぶ progress.Report(new ProgressResult { Percentage = 1, Value = start }); try { var s = await TaskEx.Run(() => HeavyHeavyHeavyMethod(start)); token.ThrowIfCancellationRequested(); // キャンセルされた場合は例外送出 progress.Report(new ProgressResult { Percentage = 33, Value = s }); s = await TaskEx.Run(() => HeavyHeavyHeavyMethod(s)); token.ThrowIfCancellationRequested(); // キャンセルされた場合は例外送出 progress.Report(new ProgressResult { Percentage = 66, Value = s }); s = await TaskEx.Run(() => HeavyHeavyHeavyMethod(s)); token.ThrowIfCancellationRequested(); // キャンセルされた場合は例外送出 listBox1.Items.Add("終わった、結果:" + s); } catch (OperationCanceledException) { listBox1.Items.Add("キャンセルされたー"); } } public MainWindow() { InitializeComponent(); // プログレスが変化したときの挙動の登録 var progress = new EventProgress<ProgressResult>(); progress.ProgressChanged += (sender, e) => listBox1.Items.Add(e.Value.Percentage + "%" + ":" + e.Value.Value); // キャンセルボタンを押したとする、時にキャンセルする var ctsSource = new CancellationTokenSource(); button1.Click += (_, __) => ctsSource.Cancel(); // 非同期実行 DoAsync("hoge", ctsSource.Token, progress); }
例外送出という形なので、BackgroundWorkerよりはキャンセルが楽です。プログレスに関しては、EventProgress<T>を用意して、それのReportメソッドを呼ぶという形になります。これはBackgroundWorkerに非常に近い感じですね。
同期→非同期
今まで見た「重い処理」であるHeavyHeavyHeavyMethodは同期的なものでした。言うならばWebRequestのGetResponse。もしくはCPU時間を喰う処理。では、BeginGetResponseのような、重い処理が非同期の場合の非同期処理(こんがらがる)はどうなるでしょう。
void HeavyMethod2(string s, Action<string> action) { ThreadPool.QueueUserWorkItem(_ => { Thread.Sleep(5000); var result = s + s; action(result); }); }
こんな、なんちゃって非同期メソッドがあるとして、こいつをどう料理出来るか。
非同期とBackgroundWorker
元から非同期のものに対し、BackgroundWorkerは無力です。破綻です。さようならです。
// DoWorkは実行されるとすぐに抜けて(HeavyMethod2が非同期のため) // RunWorkerCompletedが呼ばれることになって全く正常に動かない bw.DoWork += (sender, e) => { HeavyMethod2("hoge", s1 => { bw.ReportProgress(33, s1); HeavyMethod2(s1, s2 => { bw.ReportProgress(66, s2); HeavyMethod2(s2, s3 => { bw.ReportProgress(100, s3); }); }); }); }; bw.RunWorkerCompleted += (sender, e) => { var result = e.Result; listBox1.Items.Add("終わった、結果:" + result); }; bw.RunWorkerAsync("hoge");
これはちっとも動きません。というかReportProgressで例外が出ます(実行が完了=RunWorkerCompletedが呼ばれている状態ではReportProgressは呼べない)。なんとも、ならないですねえ。ここでAutoResetEventなどを呼んでDoWorkの完了を待機してやるぜ、という策もありますが、そんなことをやる意味は全くないでしょう。
Reactive Extensions
補助拡張メソッドとしてXxxAsObservableを定義しましょう。Begin-EndパターンのものならFromAsyncPatternが使えますが、今回のような俺々非同期メソッドには使えないので、AsyncSubjectを使って自前でラップします。
IObservable<string> HeavyMethod2AsObservable(string input) { var asyncSubject = new AsyncSubject<string>(); HeavyMethod2(input, s => { try { asyncSubject.OnNext(s); asyncSubject.OnCompleted(); } catch(Exception e) { asyncSubject.OnError(e); } }); return asyncSubject.AsObservable(); }
ラップ自体はそんなに難しいものでもないですし、定型なので割と楽です。AsyncSubjectの詳細、もしくは何故AsyncSubjectを使わなければならないのか、非同期ラップの落とし穴、的なものは以前の記事を参照してください。
var disposable = Observable.Return("hoge") .Report(s => reportProgress(1, s)) .SelectMany(HeavyMethod2AsObservable) .Report(s => reportProgress(33, s)) .SelectMany(HeavyMethod2AsObservable) .Report(s => reportProgress(66, s)) .SelectMany(HeavyMethod2AsObservable) .Report(s => reportProgress(100, s)) .ObserveOnDispatcher() .Subscribe( s => listBox1.Items.Add("終わった、結果:" + s), e => { listBox1.Items.Add("例外出たー"); listBox1.Items.Add(e); }, () => { });
同期のものと見比べてもらうと分かりますが、ほとんど変わりません。SelectをSelectManyに変えただけです。同期だとか非同期だとか、そんなの全く関係なく同じように取りまとめられてしまう。これはRxの強みの一つです。
async/await
RxでAsyncSubjectを使ってラップしたように、こちらではTaskCompletationSourceを使ってラップします。詳細はRxを使って非同期プログラミングを簡単にで。そうしたら、後は以前のものと同じように書きます。同じなので割愛。
まとめ
BackgroundWorkerの成したことは大きいと思います。全く非同期を意識させずにコントロールのポトペタで、UIをブロックしないコードが書ける。でもその反面、受け渡しがobjectであったりと、弊害と限界が見えているように思えます。そしてそれは、非同期APIしかないSilverlightでついに限界を向かえた。もうそろそろ、お役御免。しょうがない。
では代わりに何を使うかと言ったら、Rxを使えばいいんじゃないでしょうか、いやこれは本気で。見てきたとおり、十分にBackgroundWorkerの機能を代替出来ていますし。TaskはSilverlightにはまだ入ってないし、素のままでは使いやすいとは言い難い。目の前に現実的な解が転がっているのだから、とりあえず使ってみるのもいいんじゃないかな。機能的にはReactive Extensionsがイケてるのは間違いないと思うので(キャンセルの容易さは非常に大きい!)、そして、現実的に使える形で提供されている状態でもあるので、Rx使うといいんぢゃないかな(そればっか)。
今後。私は、Reactive Extensionsとasync/awaitは共存するものだと思っています。そして、どちらも、必須であると、両者を知れば知るほど思い始めています。なので、もう単純に比較してどうこうはお終い。次は連携を考えていきたいと思います。とりあえず、何で共存するのか、何故に両者が必須であるのか(私であるのならばRxだけじゃダメなんですか!ダメなんです、の理由などなどり)は、そのうち書きます。
Reactive Extensionsとエラーハンドリング
例外処理は非常に大事だけれど、非常に難しい。非同期となると、なおのこと難しくなる!一体どう処理したらいいものか。勿論、放置するわけにもいかない避けては通れない話。そのため、Reactive Extensionsには豊富な例外処理手段が用意されています。決してOnErrorだけではありません。想像を遥かに越える、恐るべき柔軟さがそこにはあります。そして、これもまた、何故Rxを使うべきなのか、の強い理由の一つになるでしょう。
OnError
まずは基本の例から。なお、DownloadStringAsyncはReactive Extensions用のWebRequest拡張メソッドからです。BeginGetResponseしてStreamをReadToEndしたもの、と思ってください。この辺も書いてると長くなるし本質的には関係ないので。「非同期で文字列(HTML)をダウンロード」程度の意味で。
// 存在しないアドレスにアクセスしようとする(当然例外が起こる!) // 出力結果は「リモート名が解決出来ませんでした」など。Timeoutを待つ必要はあります。 WebRequest.Create("http://goooooogllle.co.jp/") .DownloadStringAsync() .Subscribe( s => Console.WriteLine(s), // OnNext e => Console.WriteLine(e.Message), // OnError () => Console.WriteLine("completed!")); // OnCompleted // OnNextのみの場合はcatchされずに例外が外にthrowされる WebRequest.Create("http://goooooogllle.co.jp/") .DownloadStringAsync() .Subscribe(Console.WriteLine);
存在しないアドレスにアクセスしたため、WebExceptionが発生します。Rxにおける基本の例外処理は、Subscribe時にOnErrorにエラー時処理を書くこと。ここにシーケンス内で発生した例外が集められ、一括で処理出来ます。
ところで、Subscribeのオーバーロードは多数用意されている、ように見えて実のところ一つしかありません。IObserver<T>を受け取るものただ一つ。では、普段やっているAction<T>を一つだけ渡しているのは何なの?というと、OnNextだけ定義されたIObserver<T>を作るショートカットにすぎません。挙動としては、OnErrorを省略した場合は例外をcatchせずそのままthrowされていきます。OnErrorを定義した場合は、ここでExceptionを丸ごとcatch。
OnErrorで丸ごとキャッチというのは、try-catch(Exception e)のようかもしれません。例外は何でもキャッチはダメ、なるべく上の階層で処理すべき、というセオリーから考えると違和感も?ただ、同期的に書いた場合は下位層でcatchせず、最上位でcatchしよう、という話が成立しますが、非同期には最上位などというものはなく、OnErrorで掴まなければ集約例外ハンドラ行きとなるので、最上位の呼び出しメソッドなどというものはなく、そんな当てはまるものでもないかもです。というか、つまりはOnErrorが最上位のcatchの役割を担っているわけですね。
Catch
出てくる例外によって処理内容を変えたかったり、例外の種類によってはCatchしないで欲しかったりするシチュエーションはいっぱいあります。その場合OnErrorでとりあえずExceptionを取ってe is HogeException… などと分岐、というのは格好悪い。というわけで、Catchメソッド。
// TwitterのUserTimeLineは認証(OAuth)が必要なので例外が発生する! // 出力結果は、以下のレスポンス // {"error":"This method requires authentication.","request":"\/statuses\/user_timeline.json"} WebRequest.Create("http://twitter.com/statuses/user_timeline.json") .DownloadStringAsync() .Catch((WebException e) => e.Response.DownloadStringAsync()) .Subscribe(Console.WriteLine);
Catchに渡すメソッドは型が指定出来て、型が一致しない例外はCatchしません。そして、ここで少し面白いのが渡すメソッドの戻り値はIObservable<T>でなければならないということ。例外が発生した場合は、後続に代わりのシーケンスを渡すことが出来ます。
例えばWebRequestではエラー原因を知るため、WebExceptionからResponseを取ってデータを取得したいわけです。そこで、そのままWebException中のResponseStreamから非同期で読み取ってそのまま流す。という例が上のコードです。WebAPIを試してる間はこうやってエラーメッセージが簡単に読み取れると非常に楽。OAuthとかややこしくて中々認証通せなくて泣きますからね……。
Empty/Never
Catchしたら例外処理する(ログに書くなどなど)だけで、別に後続に渡したいものなどない。という場合はEmptyを渡してやりましょう。
// 5が出たら例外出してみる // 出力結果は1,2,3,4,completed Observable.Range(1, 10) .Do(i => { if (i == 5) throw new Exception(); }) .Catch((Exception e) => Observable.Empty<int>()) .Subscribe(i => Console.WriteLine(i), e => { }, () => Console.WriteLine("completed"));
他に同種のものとして、Neverがあります。
// Neverは何も返さない物 // Emptyとの違いは、OnCompletedすら発生しない // 余談ですが、FromEventの第一引数は最新情報によると h => h.Invoke が一番短く書けてお薦めです! var collection = new ObservableCollection<int>(); Observable.FromEvent<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEventArgs>( h => h.Invoke, h => collection.CollectionChanged += h, h => collection.CollectionChanged -= h) .Select(e => (int)e.EventArgs.NewItems[0]) .Do(i => { if (i == -1) throw new Exception(); }) .Catch((Exception e) => Observable.Never<int>()) .Subscribe(Console.WriteLine, e => { }, () => Console.WriteLine("終了")); // 出力結果は 300 collection.Add(300); // 300が出力される collection.Add(-1); // 例外発生 collection.Add(1000); // デタッチ済みなので何も発生しない
NeverはOnCompletedを発生させないという性質が、Emptyとの使い分けとして面白いかもしれません。
OnCompletedとFinallyの違いについて
Catchがあるなら、当然Finallyもあります!そこでふと思うFinallyとOnCompletedってどう違うの?というと、OnErrorとの絡みで違います。
// 出力結果は 例外発生, ふぁいなりー Observable.Throw<int>(new Exception()) // Throwは例外のみを出すというもの .Finally(() => Console.WriteLine("ふぁいなりー")) .Subscribe( i => Console.WriteLine(i), e => Console.WriteLine("例外発生"), () => Console.WriteLine("こんぷりーてっど"));
Reactive Extensionsにおいて守られている、また、もし自分で拡張メソッドを書く場合などに守らなければならない原則があります。それは「OnErrorとOnCompletedはどちらか一つのみが発生する」「OnError/OnCompleted後はOnNextは発生しない」という点。というわけで、FinallyとOnCompletedの違いですが、例外発生時にも実行されるのがFinally、そうでないのがOnCompletedといったところです。
また、Finallyは途中で挟むのでメソッドチェーンの並びによっては実行されるタイミングを調整出来るのもポイントです。例えば
// 1, 2, 3, ふぁいなりー1, 100, 101, 102, ふぁいなりー2 Observable.Range(1, 3) .Do(Console.WriteLine) .Finally(() => Console.WriteLine("ふぁいなりー1")) .TakeLast(1) .SelectMany(i => Observable.Range(100, 3)) .Finally(() => Console.WriteLine("ふぁいなりー2")) .Subscribe(Console.WriteLine);
TakeLast(1)は最後の1つのみを取得する、そのためにそれ以前のものは「完了」していなければならない。完了したということはFinallyが発動する。というわけで、SelectrMany後の、SubscribeされているOnNextに届く前に一つ目のFinallyが実行されます。二つ目のFinallyに関しては、全てのシーケンスが列挙された最後となります。
この性質は、Streamなど適切にClose/Disposeしなければならないものの実行タイミングの調整に使えます。
OnErrorResumeNext
Catchと同じような性質を持つメソッドとして、OnErrorResumeNextがあります。両者の違いは、例外非発生時に現れます。
// 実行結果は「1, 2, 3」Catchは例外非発生時は後続を渡さない Observable.Range(1, 3) .Catch(Observable.Range(100, 3)) .Subscribe(Console.WriteLine); // 実行結果は「1, 2, 3, 100, 101, 102」OnErrorResumeNextは例外非発生時も後続に繋ぐ Observable.Range(1, 3) .OnErrorResumeNext(Observable.Range(100, 3)) .Subscribe(Console.WriteLine);
代わりに渡す後続が、例外非発生時にも渡されるのがOnErrorResumeNext、渡さないのがCatch。つまりOnErrorResumeNextは必ず後続が繋がれるので、Catch().Concat()、で表現できます。
Retry/Timeout
Webにアクセスする時って失敗したらリトライしたいですよねー、特にTwitterなんて普通にサーバー不調でエラー返してきやがりますからね!という時はRetry。
// Access Start x3回のあとに401例外発生 Observable.Defer(() => { Console.WriteLine("Access Start:"); return WebRequest.Create("http://twitter.com/statuses/user_timeline.json").DownloadStringAsync(); }) .Retry(3) .Subscribe(Console.WriteLine);
再アクセスしているのが分かるようコードが少しゴチャついてしまいましたが……。この例では認証が必要なものにアクセスしているため、100% WebExceptionが発生してます。んが、Retry(3)ということで、3回リトライしています。リトライ処理は必須ではあるものの、面倒くさいものの筆頭でしたが、恐ろしく簡単に書けてしまいました。同様に、タイムアウトもあります。
// Timeoutは指定時間以内に値が通過しなければTimeoutExceptionが発生します // カウントのタイミングはSubscribeされたらスタート // この例では恐らく例外発生します(100ミリ秒で結果を返せる、ことは恐らくないでしょふ) var wc = new WebClient(); Observable.FromEvent<DownloadStringCompletedEventHandler, DownloadStringCompletedEventArgs>( h => h.Invoke, h => wc.DownloadStringCompleted += h, h => wc.DownloadStringCompleted -= h) .Timeout(TimeSpan.FromMilliseconds(100)) // 100ミリ秒 .Take(1) // これつけないとTimeoutのチェックが働き続けます←WebClientもWebRequest感覚になるようにしたほうがRx的にはお薦め .Subscribe(e => Console.WriteLine(e.EventArgs.Result)); wc.DownloadStringAsync(new Uri("http://bing.com/"));
Web関連だとWebRequestであればTimeoutがありますが、ないもの(WebClient)もありますし、また、こちらのほうがお手軽なので、それなりに重宝すると思います。勿論、Web関連以外のメソッドでも使えますので、例えば一つのボタンが押されて、指定時間内にもう一つのボタンが押されなければTimeoutExceptionを出す、などなども考えられますね。更にそれをTimeoutExceptionをCatchで、指定時間内で二つのボタンが押されなかった場合の処理を追加、などなど、従来面倒くさかったことが恐ろしくスッキリ記述出来ます。
まとめ
ReactiveOAuthが機能的にスッカラカンなのは手抜きじゃなくて、(Retryとか)Rxに丸投げ出来るからです。ひたすら機能は分解、モジュールは小さく。というのは大変関数型的だと思うのですが、まさにそれです。(ただ、ちょっと、というかかなり手直ししないとマズいところがあるので近いうちに更新します……)
といったわけで、Rxはかなり柔軟にエラーを処理することが出来るため、もう同期とか非同期とか関係なく、Rx挟まないでネットワーク処理書くのは面倒くさくて嫌です、という勢いだったり。大変素晴らしい。というかもう全ての処理をRx上に載せてしまってもいいんじゃね?ぐらいの勢いだったりしますがそれはやりすぎである。
なお、この記事はC# Advent Calendar jp: 2010の12/4分として書きました。リレーが途切れると、寂しいです。最後まで完走させたいですね。JavaScriptなど割とすぐに埋まったのに、こんなところで言語の(日本のネット上での)人気不人気が視覚化されるとしたら、寂しい話です。
あと、私はJavaScript Advent Calendar 2010のほうでも12/20に書くので、20日もよろしくどうも。linq.jsとRxJSについて書きます。