.. :tocdepth:2 ======================================= Reactive ======================================= 背景 ======================================= ここでは MoNo.FSharp.dll に定義されている Reactive の機能について説明します。 WPF は Data Binding という優れた機構を備えています。 MoNo.RAIL の Reactive を利用すると、Data Binding をより手軽に行うことが出来るようになります。 ただし、Reactive は WPF に依存したライブラリではありません。 WPF とは無関係に動かすことが可能です。 ここでは LINQPad を利用して Reactive の動きを確認していきます。 準備 ======================================= LINQPad の [Query | References and Properties] メニューから設定ダイアログを開き、下記の設定をします。 * Additional References タブの Add ボタンで MoNo.FSharp.dll を参照に追加します * Additional Namespace Imports タブに MoNo namespace を追加します Reactiveの挙動 ====================================== Reactiveの挙動を確認するために以下のコードを実行してみましょう。 .. code-block:: fsharp let x = Reactive 0 x |> Observable.add (printfn "%d") x.Val <- 1 x.Val <- 2 次のような出力が確認できると思います。 .. code-block:: console 0 1 2 値が変更されるとObservable.addで渡されたハンドラにその値が渡されていることが確認できます。 また、変更前の0が出力されていることにも注意して下さい。Observableにaddした時点での値もハンドラに渡されます。 `Observable.add `_ のシグネチャを見て下さい。 .. code-block:: fsharp Observable.add : ('T -> unit) -> IObservable<'T> -> unit ハンドラとIObservable<'a>を渡す関数であることがわかります。 つまりReactive<'a>は `IObservable\<'a\> `_ を実装しているのです。 Reactive.map ====================================== Reactiveモジュールにはmap関数も用意されています。シグネチャを確認してみましょう。 .. code-block:: fsharp Reactive.map: ('a -> 'b) -> source:#IReactive<'a> -> IReactive<'b> では実際にReactive.mapによってどのような挙動をするか確認してみます。 .. code-block:: fsharp let x = Reactive 0 let y = x |> Reactive.map ((*) 2) y |> Observable.add (printfn "%d") x.Val <- 1 x.Val <- 2 .. code-block:: console 0 2 4 xの値(Val)を変更するとmapに渡された関数によって2倍に変換された値がy.Valに設定され、さらにその値がObservable.addで指定したハンドラに渡されていることがわかります。 Reactive.bind ====================================== Reactive変数xとyの値を変更しても常にその和を値として持つようなReactive変数zを作りたいとします。 .. code-block:: fsharp let x = Reactive 0 let y = Reactive 0 let z = ...// 常にx+yになるReactive変数を作るには? mapを使って実現できるでしょうか。 .. code-block:: fsharp let x = Reactive 0 let y = Reactive 0 let z = x |> Reactive.map (fun x -> y |> Reactive.map (fun y -> x + y)) z |> Observable.add (printfn "%d") // コンパイルエラー x.Val <- 1 y.Val <- 2 Observable.addをしている行がコンパイルエラーとなります。 これはzがIReactive型ではなくIReactive>型となってしまっているからです。 ここでReactive.bindを使用します。先のコードを以下のように修正して下さい。 .. code-block:: fsharp let x = Reactive 0 let y = Reactive 0 let z = x |> Reactive.bind (fun x -> y |> Reactive.map (fun y -> x + y)) z |> Observable.add (printfn "%d") x.Val <- 1 y.Val <- 2 出力は以下の通りです。xやyの値を変更するとzの値は常にx+yとなっていることがわかります。 .. code-block:: console 0 1 3 MoNo.RAILには上記のコード相当を記述できるreactiveコンピューテーション式が用意されています。 このコンピューテーション式を使うと前述のコードは以下のように書くことができます。 .. code-block:: fsharp let x = Reactive 0 let y = Reactive 0 let z = reactive { let! x = x let! y = y return x + y } z |> Observable.add ... ここではコンピューテーション式の詳細には触れませんが、reactiveコンピューテーション式内部では * let!によってReactive<'a>型に格納されている'a型のValが取り出され * returnによって'a型の値がIReactive<'a>のValに格納され返される と考えて差し支えありません。 Reactiveの構造 ====================================== Reactiveがどのような構造になっているか見てみましょう。 .. image:: reactiveuml.png 抑えておくべきポイントは以下のとおりです。 * IReactive<'a>ではValはreadonlyですがIReactiveW<'a>では読み書き可能です。 * IObservable<'a>を実装しているためObserver<'a>の監視対象にできます。 * INotifyPropertyChangedを実装しており、Valの変更に対して通知イベントが発火するようになっているためWPFでの双方向バインディングが容易に実現できます。