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の挙動を確認するために以下のコードを実行してみましょう。

let x = Reactive 0
x |> Observable.add (printfn "%d")
x.Val <- 1
x.Val <- 2

次のような出力が確認できると思います。

0
1
2

値が変更されるとObservable.addで渡されたハンドラにその値が渡されていることが確認できます。 また、変更前の0が出力されていることにも注意して下さい。Observableにaddした時点での値もハンドラに渡されます。

Observable.add のシグネチャを見て下さい。

Observable.add : ('T -> unit) -> IObservable<'T> -> unit

ハンドラとIObservable<’a>を渡す関数であることがわかります。 つまりReactive<’a>は IObservable<’a> を実装しているのです。

Reactive.map

Reactiveモジュールにはmap関数も用意されています。シグネチャを確認してみましょう。

Reactive.map: ('a -> 'b) -> source:#IReactive<'a> -> IReactive<'b>

では実際にReactive.mapによってどのような挙動をするか確認してみます。

let x = Reactive 0
let y = x |> Reactive.map ((*) 2)
y |> Observable.add (printfn "%d")
x.Val <- 1
x.Val <- 2
0
2
4

xの値(Val)を変更するとmapに渡された関数によって2倍に変換された値がy.Valに設定され、さらにその値がObservable.addで指定したハンドラに渡されていることがわかります。

Reactive.bind

Reactive変数xとyの値を変更しても常にその和を値として持つようなReactive変数zを作りたいとします。

let x = Reactive 0
let y = Reactive 0
let z = ...// 常にx+yになるReactive変数を作るには?

mapを使って実現できるでしょうか。

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<int>型ではなくIReactive<IReactive<int>>型となってしまっているからです。

ここでReactive.bindを使用します。先のコードを以下のように修正して下さい。

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となっていることがわかります。

0
1
3

MoNo.RAILには上記のコード相当を記述できるreactiveコンピューテーション式が用意されています。 このコンピューテーション式を使うと前述のコードは以下のように書くことができます。

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がどのような構造になっているか見てみましょう。

../../_images/reactiveuml.png

抑えておくべきポイントは以下のとおりです。

  • IReactive<’a>ではValはreadonlyですがIReactiveW<’a>では読み書き可能です。
  • IObservable<’a>を実装しているためObserver<’a>の監視対象にできます。
  • INotifyPropertyChangedを実装しており、Valの変更に対して通知イベントが発火するようになっているためWPFでの双方向バインディングが容易に実現できます。