C#とRxでリアクティブプログラミングしてみた。


※ C#初心者なので、コードで至らない点ありましたコメント頂けたら助かります。

なんだそれ

Rx

Reactive Extensionsというライブラリ。
簡単に紹介すると、Linqライクな構文を持つ 特定のシーケンス の管理が出来るもの (と認識している。)

詳細はググって下さい。

リアクティブプログラミング

結構、抽象的な概念だったりするので、私からの説明は割愛。
RP(Reactive Programming) や FRP(Functional Reactive Programming)

などがあります。
それを区分けするには、未だ理解度が足りない。

詳細はググって下さい。

開発環境の準備

私が使用している端末はMacなので Xamarin Studio というIDEを使う事にしました。
VisualStudio や MonoDevelopなどでも同様な事が出来ると思います。

C#のコンソールアプリケーションを作り、add packagesからズドンと
Reactive Extesions - Main Library
を突っ込みます。

早速コードを書こう。

シーケンス管理と言う事で、 私の休日のシーケンスを、リアクティブプログラミングで表現しました。

ドスン

using System;
using System.Collections.Generic;
using System.Reactive.Linq;

namespace Rx
{
    //  ステータス管理型
    using StateMap = Dictionary<string, Dictionary<string, Action>>;
    class MainClass
    {
        public static void Main (string[] ars)
        {
            //  ステータス定義
            const string AWAKE      = "AWAKE";
            const string EAT        = "EAT";
            const string NET_SURFIN = "NET_SURFIN";
            const string SLEEP     = "SLEEP";

            //  ステータス上で使われる、呼び出し関数の想定
            const string ON_ENTER   = "onEnter";
            const string UPDATE     = "update";
            const string ON_EXIT    = "onExit";

            var stateMap = new StateMap ();

            //  初期化用delegate、仮でステート名と呼び出し関数名を表示する。
            //  Updateでsleepをしているのは、処理してる雰囲気出すため。意味は無い。
            Action<string, StateMap> stateInit = delegate(string stateStr, StateMap obj) {
                var action = new Dictionary<string, Action>();
                action.Add(ON_ENTER, () => Console.WriteLine(stateStr + ":" + ON_ENTER));
                action.Add(ON_EXIT, () => Console.WriteLine(stateStr + ":" + ON_EXIT));
                action.Add(UPDATE, delegate {
                    Console.WriteLine(stateStr + ":" + UPDATE);
                    System.Threading.Thread.Sleep (1000);
                });
                obj.Add(stateStr, action);
            };

            //  各種ステートで実際に初期化
            stateInit (AWAKE     , stateMap);
            stateInit (EAT       , stateMap);
            stateInit (NET_SURFIN, stateMap);
            stateInit (SLEEP     , stateMap);

            //  Rxにステート毎のシーケンスを登録します。
            IObservable<Action> provider = Observable.Create<Action> (obj => {
                foreach (var state in stateMap) {
                    obj.OnNext (() => state.Value[ON_ENTER]());
                    obj.OnNext (() => state.Value[UPDATE]());
                    obj.OnNext (() => state.Value[ON_EXIT]());
                }
                obj.OnNext (() => Console.WriteLine ("all sequence success."));
                obj.OnCompleted ();
                return System.Reactive.Disposables.Disposable.Empty;
            });

            //  実行!
            provider.Subscribe (sequence => sequence ());
        }
    }
}

AWAKE:onEnter
AWAKE:update
AWAKE:onExit
EAT:onEnter
EAT:update
EAT:onExit
NET_SURFIN:onEnter
NET_SURFIN:update
NET_SURFIN:onExit
SLEEP:onEnter
SLEEP:update
SLEEP:onExit
all sequence success.

何故か、哀しくなりますね。

こういう使い方が正しいのかどうか分かりませんが、ステートマシンの代わりになるってだけで良いのではないでしょうか。

まだ試していませんが、各種EventやwebAPIの通信などでも、Rxを使えば 一つのシーケンスとして 実装が出来るそうです。
すごい。

脱・コールバックネスト地獄!

勉強中なので今回はここまで。