読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

F#でRxる。よく訓練されたF#erはコンピューテーション式をつくる。

プログラミング F#2.0 F# C#4.0 C# Rx LINQ リアクティブプログラミング WPF コンピューテーション式 非同期 NuGet

いにしえからの言い伝えによると、よく訓練されたF#erはコンピューテーション式をつくるそうです。


Select Many: Reactive Extensions’ Mother Of All Operators (Chaining). お、SelectManyですね。
なるほどなるほど。計算を繋ぐもの。モナドですか。そこでコンピューテーション式(Computation expressions)ですねわかります。


リアクティブプログラミングでリア充
リアクティブプログラミングとは何か。「Reactive Programming」を直訳すると「反応的なプログラミング」です。
その名の通り、反応的に作用するようにプログラミングをすることです。よくわかりませんね。


具体的には、値(あるいは状態)を直接的に扱わないで、「時間とともに変化する値(状態)」と「振る舞い(behavior)」の関係性に着目して、
宣言的にプログラムを表現するパラダイム。あるいは、それを軸としたプログラミングスタイルです。
リアクティブプログラミングは、値(状態)やIO、あるいは時間に対する振る舞いをマッピングし、
計算の遅延や合成、差分適用、非同期処理の扱いなどに長けています。(.NETでは、Rx(Reactive Extensions for .NET)を利用するなどで実現できる。)
Functional Reactive Programming(FRP)は、これを関数型の世界に持ってきたものです*1
リアクティブプログラミングは先進的で、アカデミックな雰囲気が強いので、少しとっつきにくい所もありますが、
理解して使いこなせるようになれば、未来は明るいです。藤本幸世もびっくりのモテキ到来も夢じゃないです(ギークにモテモテ的な意味で)。



Rx(Reactive Extensions for .NET)とは
Rx(Reactive Extensions for .NET)は、LINQ to Objectsの数学的な意味の双対で、LINQシーケンスベースに、非同期イベント処理などのリアクティブな記述を、
pushベース と pullベース の両面からサポートするリアクティブフレームワークです。
最近では、「NuGet(ぬげっと)」でお手軽に導入できるようにもなりましたし、Windows Phone 7に標準搭載もされました。
まだまだ国内の情報は少ないのですが、非常に便利で強力なフレームワークなので利用しない手はないです。
Rxの日本語情報に関しては、@neueccさんのブログの記事がもっとも充実していると思います。ステキです。助かります!


@neueccさんのRxカテゴリ - neue.cc
http://neue.cc/category/programming/rx



逆に考えるんだ。IEnumerableがpullなら、IObservableはpush

Rxを理解する最も簡単な方法のひとつは、おそらくIEnumerableとIObservableを比較してみることです。
以下の例は、短いIEnumerableシーケンス内のすべての奇数を見つけて出力するだけのシンプルなC#のコード。

var oddNumbers = Enumerable.Range(1, 10).Where(n => n % 2 == 1);
foreach (int n in oddNumbers)
{
    Console.WriteLine(n);
}

変数oddNumbersへ代入した時点では、まだ計算は評価されません。
foreachによってIEnumerableを展開するときにはじめて計算が行われます。
foreachが何をしてくれているかというと、実際は以下の糖衣構文です。

var enumerator = Enumerable.Range(1, 10).Where(n => n % 2 == 1).GetEnumerator();
while (enumerator.MoveNext())
{
    Console.WriteLine(enumerator.Current);
}

MoveNextメソッドの呼び出しは、その都度データを“引いています(pull)”。
IEnumerableは“pull”モデルな遅延評価です。



Rxを利用して同じ結果を得るには、例えば以下のように書きます。

var oddNumbers2 = Observable.Range(1, 10).Where(n => n % 2 == 1);
oddNumbers2.Subscribe(x => Console.WriteLine(x));

Rxを知らないで見た場合、ぱっと見何が起こったのかわかりませんが、
この方法でも同じ結果が得られます。


では、一体何がおこったのでしょう。
以下は、oddNumbers2がやっていることをイメージしやすい(?)ように、別の方法で実装してみた例です。
(oddNumbers2の糖衣構文ではありませんので注意してください)

var oddNumbers3 = Observable.CreateWithDisposable<int>(observer =>
    {
        var cancel = new CancellationTokenSource();
        foreach (var n in Enumerable.Range(1, 10))
        {
            if (!cancel.Token.IsCancellationRequested)
            {
                observer.OnNext(n); continue;
            }
            observer.OnCompleted();
            break;
        }
        return cancel;
    });
oddNumbers3.Where(n => n % 2 == 1).Subscribe(x => Console.WriteLine(x));


IObservableは、いわゆる「Observerパターン」の一種で、IEnumerableの動作を逆にして考えたものです。
ObservableがObserverの OnNextメソッドを呼び出すことは、すなわちIEnumerableに情報を与えるための yieldキーワード に相当します。
これは、Observableが、OnNextメソッドによって、Observerに対してデータを“押し出し(push)”ています。
また、IObservableがObserverの OnCompletedメソッドを呼び出すことは、
IEnumrableで、もうこれ以上データがないことを表す breakキーワード に相当します。ちょうど逆になっています。


さて、説明を試みようとしましたが、感覚的にしかわかっていないので、うまく説明できませんorz
難しいことはよくわかりませんが、IObservableは pushモデル なので、とっても良いものらしいです。
何が良いのかというと、イベントや非同期処理のハンドリングに役立つようです。
例えば、IObserverをマウスイベントにアタッチして、それらを非同期的に記録したり、LINQを利用して反復処理をすることができたり。
これすなわち、事実上のモナドであり、リアクティブプログラミングを可能にしているというわけです。*2


ご存知の方はご存知のとおり、Rxの向こう側には素晴らしい世界が広がっています。が、その学習コストは大きな負担になるかもしれません。
一般的にプログラムを書く場合というのは、プロアクティブ*3なスタイルが主流なので、パラダイムシフトにはそれなりの痛み*4を伴うことが予想されます。
しかしながら、学習コストに見合っただけのリターンがRxには必ずあります。勉強して損はしません。というかしないと損します。


F#でRxる

さて、C#でRxと戯れるのもよいのですが、C#だけRxと仲良しなのはずるい*5
F#でリアクティブプログラミングがしたいんです。Functional Reactive Programming (FRP)がしたいんです。(`・ω・´)


F#では、なんとEvent(イベント)がファーストクラスオブジェクトなのです(C#とは違うのだよC#とは)。
ですので、Rxに頼らずに素のままのF#でも、ある程度リアクティブプログラミングの実践が可能となっています。
ただし、基本的なこと(mapやfilterやmergeなど)はできるものの、できることには限りがあります。
Control.Event モジュール(F#)がサポートする内容は、Rxの充実具合に比べて、どうしても見劣りしてしまいます。
http://msdn.microsoft.com/ja-jp/library/ee340422.aspx


ということで、「F#でRxる(F#からRxを使う)」ことにしました。Rxも.NETですので、もちろんF#から扱うことができます。
当然そのまま利用することもできますが、F#は関数型のパラダイムを軸とした関数型言語でありますから、
リアクティブプログラミングに関しても、できることならFunctionalに扱いたいんです。それがF#erの性(saga)というものです。
非常に面倒くさいですが、Rxの関数郡*6をF#の関数でラップしてFRP可能にします。パフォーマンスのことは考えません(キリッ


Rx.fs

namespace FSharp

module Rx =
  open System
  open System.Linq
  open System.Threading
  open System.Windows.Threading

  let asAction f = new System.Action(f)
  let doNothing = asAction (fun () -> ())

  /// Observable.Create
  let create f =
    Observable.Create<_>(fun x ->
        f x
        doNothing)

  let create2 f =
    let subscribe = Func<_,_>(fun x -> Action(f x))
    Observable.Create(subscribe)

  /// Observable.CreateWithDisposable
  let createWithDisposable f =
    let subscribe = Func<_,_>(f)
    Observable.CreateWithDisposable(subscribe)
  
  /// Observer.Create
  let createObserver next error completed =
    Observer.Create(Action<_>(next))
  let createObserver2 next error =
    Observer.Create(Action<_>(next), Action<exn>(error))
  let createObserver3 next completed =
    Observer.Create(Action<_>(next), Action(completed))
  let createObserver4 next error completed =
    Observer.Create(Action<_>(next), Action<exn>(error), Action(completed))

  /// Observable.FromEvent
  /// F#では、Event<_,_>.Publishがあるから、もしかしていらねんじゃね的な雰囲気もある…
  let fromEvent (event:IEvent<_,_>) = create (fun x -> event.Add x.OnNext)
  let fromEvent2<'TEventArgs when 'TEventArgs :> EventArgs> addHandler removeHandler =
    Observable.FromEvent(Action<EventHandler<'TEventArgs>>(addHandler), Action<EventHandler<'TEventArgs>>(removeHandler))
  let fromEvent3<'TEventArgs, 'TDelegate when 'TEventArgs :> EventArgs 
                                         and 'TDelegate :> MulticastDelegate> conversion addHandler removeHandler =
    Observable.FromEvent(Func<EventHandler<'TEventArgs>,'TDelegate>(conversion) ,Action<'TDelegate>(addHandler), Action<'TDelegate>(removeHandler))
  let fromEvent4 addHandler removeHandler =
    Observable.FromEvent(Action<EventHandler>(addHandler), Action<EventHandler>(removeHandler))
  let fromEvent5 (event:IEvent<_,_>) =
    Observable.FromEvent(event.AddHandler, event.RemoveHandler)
  let fromEvent6 (target:obj) name =
    Observable.FromEvent(target, name)

  /// Async<_>. から Observableを生成します。
  let fromAsync a = 
    { new IObservable<_> with
        member x.Subscribe(obserber) =
          if obserber = null then nullArg "IObserver<'T>"
          let cts = new CancellationTokenSource()
          let ao = async {
            try
              let! r = a
              obserber.OnNext(r)
              obserber.OnCompleted()
            with 
              e -> cts.Cancel ()
                   obserber.OnError(e)}

          Async.StartImmediate(ao, cts.Token)
          { new IDisposable with 
              member x.Dispose() = 
                let invoked = ref 0
                if Interlocked.CompareExchange(invoked, 1, 0) = 0 then
                  cts.Dispose () }
          }

  /// Observable.SelectMany
  let selectMany source (other:'TSource -> IObservable<'TResult>) = 
    Observable.SelectMany(source, other)
  let selectMany2 f source = 
    Observable.SelectMany(source, Func<_, IObservable<'TResult>>(f))
  let selectMany3 f source = 
    Observable.SelectMany(source, Func<_, seq<'TResult>>(f))
  let selectMany4 f g source = 
    Observable.SelectMany(source, Func<_,_>(f), Func<_,_,_>(g))

  /// Observable.Catch
  let catch f source =
    (source:IObservable<'T>).Catch(Func<_,_>(f))
  let catch2 first second =
    (second:IObservable<'T>).Catch((first:IObservable<'T>))
  let catch3 source =
    Observable.Catch(source) 

  /// Observable.Finally
  let performFinally f source = Observable.Finally(source, fun _ -> f())

  /// Observable.While
  let While f source = Observable.While(Func<_>(f), source)

  /// Observable.Empty
  let empty<'T> = Observable.Empty<'T>()   

  /// Observable.Using
  let using rs ru = 
    Observable.Using(Func<_>(rs), Func<_,_>(ru));

  /// Observable.Concat 
  let concat (first:IObservable<'TSource>) second = Observable.Concat(first, second)
  let concat2 (source:seq<IObservable<'TSource>>) = Observable.Concat(source)
  let concat3 (source:IObservable<IObservable<'TSource>>) = Observable.Concat(source)
  let concat4  (source:IObservable<'TSource>) = Observable.Concat(source)

  /// Observable.Return
  let oreturn x = Observable.Return(x)
  let oreturn2 s x = Observable.Return(x, s)

  /// Observable.ToEnumerable
  let toEnumerable source = 
    Observable.ToEnumerable(source)


  (* 激しく長いのでいろいろ省略 *)


  type IObservable<'T> with
    member this.Subscribe(next) = subscribe next this
    member this.Subscribe(next, error) = subscribeWithError next error this
    member this.Subscribe(next, error, completed) = subscribeAll next error completed this


うへぇ。なげーよ…(なのでいろいろ省略)。※下の方にSkyDriveへのリンクを追加しました。



よく訓練されたF#erはコンピューテーション式をつくる。

さて、いにしえからの言い伝えによると、よく訓練されたF#erはコンピューテーション式をつくるそうです。
コンピューテーション式についてイチから説明すると、とんでもなく長くなる(というかうまく説明できない)ので端折りますが、


@mzpさんのF#プログラマのためのMaybeモナド入門 - みずぴー日記
http://d.hatena.ne.jp/mzp/20110205/monad
あたりを参照すると、いい感じに雰囲気が感じられるかと思います。



冒頭でも書きましたが、Select Many: Reactive Extensions’ Mother Of All Operators (Chaining)ということで、
SelectManyはIObservableの計算を繋ぐものだよね、と。お、モナドですか? そこでコンピューテーション式にご登場いただこうというわけです。


Rx.fsに追加で

  open System.Collections.Generic

  /// Observableコンピューテーション式
  type ObservableBuilder() =
    member this.Bind(x, f) = 
      f |> selectMany x
    member this.Return(x) = 
      oreturn x
    member this.ReturnFrom(x) = x
    member this.TryWith(a,b) = 
      catch a b
    member this.TryFinally(x, f) = 
      performFinally x f
    member this.Zero() = 
      empty
    member this.While(x,f) = 
      While f x
    member this.Using(f,g) =
      using f g 
    member this.Delay f = f () 
    member this.Combine(a,b) = 
      concat a b
    member this.For(inp,f) =
      inp |> toEnumerable |> For f 
    member this.Yield(x) = oreturn x
    member this.YieldFrom(x) = x

  let observable = new ObservableBuilder()


わーい、Observableコンピューテーション式ができたよー!
しんぷるいずべすと!


いくつかのサンプルコード

「F#でRxる」によるFRPなちょっとしたサンプルをいくつか(非同期ワークフローとの組み合わせもあり)。NUnitとFsUnitを使っています。



 


Test.fs
長すぎるので、こまかいサンプルを省略しました。

namespace FSharp.Rx.UT

open System
open System.Windows 
open System.Windows.Controls 

module Util = 
  open System.ComponentModel
  open Microsoft.FSharp.Quotations.Patterns

  type ViewModelBase () =
    let propertyChanged = DelegateEvent<PropertyChangedEventHandler>()
    let getPropertyName = function 
      | PropertyGet(expr,pi,_) -> pi.Name 
      | _ -> invalidOp "プロパティ名の取得に失敗"
    interface INotifyPropertyChanged with
      [<CLIEvent>]
      member this.PropertyChanged = propertyChanged.Publish
    member this.NotifyPropertyChanged propertyName = 
      propertyChanged.Trigger [|this; PropertyChangedEventArgs propertyName|]
    member this.NotifyPropertyChanged quotation = 
      quotation |> getPropertyName |> this.NotifyPropertyChanged

module Tests = 
  open Util
  open System
  open System.Windows.Input
  open System.ComponentModel

  type SampleViewModel () =
      inherit ViewModelBase()
      let mutable text = ""
      member this.Message
          with get () = text
          and set value = 
              text <- value
              this.NotifyPropertyChanged <@ this.Message @>

  type browseViewModel (address:string) =
      inherit ViewModelBase()
      let mutable _address = ""
      do _address <- address 
      member this.Address
          with get () = _address
          and set value = 
              _address <- value
              this.NotifyPropertyChanged <@ this.Address @>

  open System
  open System.Windows
  open System.Windows.Input
  open System.Windows.Controls
  open System.Windows.Shapes
  open System.Windows.Media
  open System.Xaml
  open System.IO
  open System.Text
  open System.Windows.Markup
  open System.Drawing
  open System.Windows.Forms 
  open System.Windows.Threading 
  open System.Windows.Media.Animation
  open Microsoft.FSharp.Core.Operators.Unchecked 
  open System.Linq
  open System.Reactive 
  open NUnit.Framework
  open FsUnit
  open FSharp
  open FSharp.Rx

  let parseXAML (xaml : string) = XamlReader.Parse(xaml)

  [<TestFixture>]
  type ``FSharp Rx selectMany`` () =
    [<Test>] 
    member test.``sample async RunSynchronously`` () =
      let r = ref 0 
      let _ = async {printfn "%d" 0; return 1} |> Async.RunSynchronously
                |> fun x -> async {printfn "%d" x; return x + 2} |> Async.RunSynchronously
                |> fun x -> async {printfn "%d" x; return x + 3} |> Async.RunSynchronously
                |> fun x -> async {r := x; printfn "%d,%s" x "owata"} |> Async.Start |> ignore

      // background wait
      Rx.bgWait (fun _ -> ()) DispatcherPriority.Background |> ignore
      !r |> should equal 6

    [<Test>] 
    member test.``selectMany01 standerd`` () =
      let r = ref 0 
      use hoge = Rx.fromAsync (async { printfn "%d" 0; return 1 })
              |> Rx.selectMany <| (fun x -> Rx.fromAsync (async { printfn "%d" x; return x + 2 }))
              |> Rx.selectMany <| (fun x -> Rx.fromAsync (async { printfn "%d" x; return x + 3 }))
              |> Rx.subscribe (fun x -> r := x; printfn "%d,%s" x "owata")
      !r |> should equal 6

    [<Test>] 
    member test.``selectMany02 Computation Expressions`` () =
      let r = ref 0
      use d = 
        observable {
          let! x = observable { printfn "%d" 0; return 1 }
          let! y = observable { printfn "%d" x; return x + 2 }
          let! z = observable { printfn "%d" y; return y + 3 }
          return z
        } |> Rx.subscribe (fun x -> r:=x; printfn "%d,%s" x "owata")
      !r |> should equal 6

    [<Test>] 
    member test.``selectMany03 Computation Expressions from async`` () =
      let r = ref 0
      use d = 
        observable {
          let! x = Rx.fromAsync (async { printfn "%d" 0; return 1 })
          let! y = Rx.fromAsync (async { printfn "%d" x; return x + 2 })
          let! z = Rx.fromAsync (async { printfn "%d" y; return y + 3 })
          return z
        } |> Rx.subscribe (fun x -> r:=x; printfn "%d,%s" x "owata")
      !r |> should equal 6

  [<TestFixture>]
  type ``FSharp Rx Computation Expressions`` () =
    [<Test>] 
    member test.``For01`` () =
      let r = ref 0
      use d = 
        observable {
          let x = Rx.fromAsync (async { return 1 })
          let y = Rx.fromAsync (async { return 2 })
          let z = Rx.fromAsync (async { return 3 })
          let a = x.Concat(y).Concat(z)

          for i in a do 
            printfn "%d" i 

          return! a
          } |> Rx.subscribe (fun x -> r:=!r+x; printfn "%d,%s" x "owata")
      !r |> should equal 6

    [<Test>] 
    member test.``For02 Yield`` () =
      let r = ref 0 
      use d = 
        observable {
          let! x = Rx.fromAsync (async { printfn "%d" 0; return 1 })
          let! y = Rx.fromAsync (async { printfn "%d" x; return x + 2 })
          let  z = Rx.fromAsync (async { printfn "%d" y; return y + 3})
          let! a = z
          for i in z -> (printfn "%d" i; a)
        } |> Rx.subscribe (fun x -> r:=x;printfn "%d,%s" x "owata")
      !r |> should equal 6

    [<Test>] 
    member test.``For03 YieldFrom`` () =
      let r = ref 0
      use d = 
        observable {
          let! x = Rx.fromAsync (async { printfn "%d" 0; return 1 })
          let! y = Rx.fromAsync (async { printfn "%d" x; return x + 2 })
          let  z = Rx.fromAsync (async { printfn "%d" y; return y + 3})
          for i in z do 
            printfn "%d" i
            yield! z
        } |> Rx.subscribe (fun x -> r:=x;printfn "%d,%s" x "owata")
      !r |> should equal 6

    [<Test>] 
    member test.``For04 YieldFrom`` () =
      let r = ref 0 
      use d = 
        observable {
          let x = Rx.fromAsync (async { return 1 })
          let y = Rx.fromAsync (async { return 2 })
          let z = Rx.fromAsync (async { return 3 })
          let a = x.Concat(y).Concat(z)
          for i in a do 
            printfn "%d" i
            yield! a
        } |> Rx.subscribe (fun x -> r:=!r+x;printfn "%d,%s" x "owata")
      !r |> should equal (6 * 3)

    (* 中略 *) 

    [<Test>] 
    [<STAThread>]
     member test.``WPF01 FSharpCube`` () = 
      let view =
        let xaml =
            @"<Window xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' 
              xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
              xmlns:SampleControls='Sample'
              Title='F# Cube' Background='SkyBlue' Height='400' Width='600' WindowStartupLocation='CenterScreen'>
                      <DockPanel>
                      <Canvas Name='canvas' Background='Transparent'>
                      <Viewport3D Name='ao' ClipToBounds='True' Width='150' Height='150' Canvas.Left='210' Canvas.Top='110'>
                        <Viewport3D.Camera>
                        <PerspectiveCamera x:Name='myPerspectiveCamera' FarPlaneDistance='15' LookDirection='0,0,-1' UpDirection='0,1,0' NearPlaneDistance='1' Position='0,0,2.25' FieldOfView='70' />
                        </Viewport3D.Camera>
                        <ModelVisual3D>
                            <ModelVisual3D.Content>
                            <Model3DGroup>
                                <DirectionalLight Color='#F9F9F9' Direction='-0.5,-0.5,-0.5' />
                                <DirectionalLight Color='#F9F9F9' Direction='0.5,-0.5,-0.5' />
                                <GeometryModel3D>
                                    <GeometryModel3D.Geometry>
                                        <MeshGeometry3D TriangleIndices='0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35' Normals='0,0,-1 0,0,-1 0,0,-1 0,0,-1 0,0,-1 0,0,-1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,0,1 0,-1,0 0,-1,0 0,-1,0 0,-1,0 0,-1,0 0,-1,0 1,0,0 1,0,0 1,0,0 1,0,0 1,0,0 1,0,0 0,1,0 0,1,0 0,1,0 0,1,0 0,1,0 0,1,0 -1,0,0 -1,0,0 -1,0,0 -1,0,0 -1,0,0 -1,0,0 ' TextureCoordinates='1,1 1,0 0,0 0,0 0,1 1,1 0,1 1,1 1,0 1,0 0,0 0,1 0,1 1,1 1,0 1,0 0,0 0,1 1,1 1,0 0,0 0,0 0,1 1,1 1,0 0,0 0,1 0,1 1,1 1,0 0,0 0,1 1,1 1,1 1,0 0,0 ' Positions='-0.5,-0.5,-0.5 -0.5,0.5,-0.5 0.5,0.5,-0.5 0.5,0.5,-0.5 0.5,-0.5,-0.5 -0.5,-0.5,-0.5 -0.5,-0.5,0.5 0.5,-0.5,0.5 0.5,0.5,0.5 0.5,0.5,0.5 -0.5,0.5,0.5 -0.5,-0.5,0.5 -0.5,-0.5,-0.5 0.5,-0.5,-0.5 0.5,-0.5,0.5 0.5,-0.5,0.5 -0.5,-0.5,0.5 -0.5,-0.5,-0.5 0.5,-0.5,-0.5 0.5,0.5,-0.5 0.5,0.5,0.5 0.5,0.5,0.5 0.5,-0.5,0.5 0.5,-0.5,-0.5 0.5,0.5,-0.5 -0.5,0.5,-0.5 -0.5,0.5,0.5 -0.5,0.5,0.5 0.5,0.5,0.5 0.5,0.5,-0.5 -0.5,0.5,-0.5 -0.5,-0.5,-0.5 -0.5,-0.5,0.5 -0.5,-0.5,0.5 -0.5,0.5,0.5 -0.5,0.5,-0.5 ' />
                                    </GeometryModel3D.Geometry>
                                    <GeometryModel3D.Transform>
                                        <RotateTransform3D>
                                            <RotateTransform3D.Rotation>
                                                <AxisAngleRotation3D x:Name='FSharpCube' Angle='0' Axis='1 0 1' />
                                            </RotateTransform3D.Rotation>
                                        </RotateTransform3D>
                                    </GeometryModel3D.Transform>
                                    <GeometryModel3D.Material>
                                        <DiffuseMaterial>
                                            <DiffuseMaterial.Brush>
                                                <VisualBrush>
                                                    <VisualBrush.Visual>
                                                    <TextBlock Text=' F#' Background='Gold' />
                                                    </VisualBrush.Visual>
                                                </VisualBrush>
                                            </DiffuseMaterial.Brush>
                                        </DiffuseMaterial>
                                    </GeometryModel3D.Material>
                                </GeometryModel3D>
                            </Model3DGroup>
                            </ModelVisual3D.Content>
                        </ModelVisual3D>
                        <Viewport3D.Triggers>
                        <EventTrigger RoutedEvent='Viewport3D.Loaded'>
                            <BeginStoryboard>
                                <Storyboard>
                                    <DoubleAnimation Name='anmRotary'
                                        Storyboard.TargetName='FSharpCube'
                                        Storyboard.TargetProperty='Angle'
                                        From='0' To='360'
                                        Duration='0:0:2'
                                        RepeatBehavior='Forever' />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>
                        </Viewport3D.Triggers>
                        <Viewport3D.RenderTransform>
                            <TranslateTransform X='0' Y='0' />
                        </Viewport3D.RenderTransform>
                        </Viewport3D>
                    </Canvas>
                </DockPanel>
            </Window> " |> parseXAML :?> Window

        let ao = xaml.FindName("ao") :?> Viewport3D
        let canvas = xaml.FindName("canvas") :?> Canvas
        let mouseMove = canvas.MouseMove |> Rx.fromEvent

        mouseMove
        |> Rx.map (fun e -> let mutable targetPoint = e.GetPosition(canvas)
                            targetPoint.X <- targetPoint.X - ao.ActualWidth / 2.
                            targetPoint.Y <- targetPoint.Y - ao.ActualHeight / 2.
                            let d = new Duration(TimeSpan.FromMilliseconds(4500.))
                            let mutable xAnimation = new DoubleAnimation()
                            xAnimation.To <- Nullable targetPoint.X 
                            xAnimation.Duration <- d
                            let mutable yAnimation = new DoubleAnimation()
                            yAnimation.To <- Nullable targetPoint.Y 
                            yAnimation.Duration <- d
                            ao.BeginAnimation(Canvas.LeftProperty, xAnimation, HandoffBehavior.Compose)
                            ao.BeginAnimation(Canvas.TopProperty, yAnimation, HandoffBehavior.Compose)
                            )
        |> Rx.catch (fun e -> printfn "%s" e.Message; Rx.never)
        |> Rx.subscribe (fun x -> x) |> ignore
        xaml

      let main() =
        (System.Windows.Application()).Run(view) |> ignore
      main()

    [<Test>] 
    [<STAThread>]
     member test.``WPF02 D&D`` () = 
      let view =
        let xaml =
            @"<Window
                xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
                xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
                Title='F#,Rx Sample' Height='250' Width='250'>
            <Canvas x:Name='canvas' Background='White'>
                <Ellipse Fill='Pink' Width='60' Height='60' Canvas.Left='50' Canvas.Top='35' Canvas.ZIndex='1'/>
                <Ellipse Fill='SkyBlue'  Width='60' Height='60' Canvas.Left='75' Canvas.Top='70'/>
                <TextBlock Canvas.Left='85' Canvas.Top='95' Text='{Binding Message}' />
            </Canvas>
        </Window>" |> parseXAML :?> Window

        let canvas = xaml.FindName("canvas") :?> Canvas
        let leftDown = canvas.MouseLeftButtonDown  |> Rx.fromEvent
        let leftUp   = canvas.MouseLeftButtonUp |> Rx.fromEvent
        let mouseMove = canvas.MouseMove |> Rx.fromEvent
        let mouseLeave = canvas.MouseLeave |> Rx.fromEvent

        leftDown
        |> Rx.map (fun e -> (e, mouseMove |> Rx.takeUntil leftUp |> Rx.takeUntil mouseLeave))
        |> Rx.filter (fun (e, _) -> e.Source.Equals(canvas) = false)
        |> Rx.map (fun (e, o) ->
                    let control = e.Source :?> UIElement
                    let location = (Canvas.GetLeft(control), Canvas.GetTop(control))
                    let pt1 = e.GetPosition(canvas)
                    o |> map (fun e -> let pt2 = e.GetPosition(canvas)
                                       (control, location, pt2 - pt1)))
        |> Rx.mergeAll
        |> Rx.subscribe (fun (ctl, (left,top), delta) -> Canvas.SetLeft(ctl, left + delta.X)
                                                         Canvas.SetTop(ctl, top + delta.Y)) |> ignore
        xaml

      let main() =
        view.DataContext <- SampleViewModel(Message="F# love")
        (System.Windows.Application()).Run(view) |> ignore
      main()

    [<Test>] 
    [<STAThread>]
     member test.``WPF03 Google Map Client`` () = 
      let view =
        let xaml = 
            @"<Window 
                xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
		            xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
            		xmlns:l='clr-namespace:FSharp.Rx.UT;assembly=FSharp.Rx.UT'
		            Title='Google Map Client' Height='600' Width='800' WindowStartupLocation='CenterScreen'>
	            <Grid Background='#ddd' >
		            <Grid.RowDefinitions>
			            <RowDefinition Height='Auto' />
			            <RowDefinition Height='*' />
		            </Grid.RowDefinitions>
		            <Grid Grid.Row='0'>
			            <Grid.ColumnDefinitions>
				            <ColumnDefinition Width='*' />
				            <ColumnDefinition Width='Auto' />
			            </Grid.ColumnDefinitions>
                        <TextBox Grid.Column='0' Name='txtSearch' Margin='8,8,4,8' Text='{Binding Address}' />
			            <Button Grid.Column='1' Name='btnSearch' Width='64' Margin='4,8,4,8'>search</Button>
		            </Grid>
		            <WebBrowser x:Name='browser' Grid.Row='1' Margin='8,0,8,8' />
	            </Grid>
            </Window>" |> parseXAML :?> Window

        let btnSearch = xaml.FindName("btnSearch") :?> System.Windows.Controls.Button
        let txtSearch = xaml.FindName("txtSearch") :?> System.Windows.Controls.TextBox
        let browser = xaml.FindName("browser") :?> System.Windows.Controls.WebBrowser 
        browser.Source <- new Uri(System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "index.htm"))
        let btnOnClick = btnSearch.Click |> Rx.fromEvent 
        let txtOnKeyDown = txtSearch.KeyDown |> Rx.fromEvent 

        let search () = browser.InvokeScript("moveMap", txtSearch.Text)

        btnOnClick
        |> Rx.map (fun x -> search ())
        |> Rx.catch (fun e -> printfn "%s" e.Message; Rx.never)
        |> Rx.subscribe (fun x -> ()) |> ignore

        txtOnKeyDown
        |> Rx.filter (fun e -> e.Key = Key.Enter)
        |> Rx.map (fun x -> search())
        |> Rx.catch (fun e -> printfn "%s" e.Message; Rx.never)
        |> Rx.subscribe (fun x -> ()) |> ignore

        xaml

      let main() =
        let b = new browseViewModel("旭川駅" )
        view.DataContext <- b
        (System.Windows.Application()).Run(view) |> ignore
      main()
      
  // nunit-gui-runner
  let main () = NUnit.Gui.AppEntry.Main([|System.Windows.Forms.Application.ExecutablePath|]) |> ignore
  main ()


最近…、MVVMという宗教がこわいです><


index.htm

<!DOCTYPE html>
<!-- saved from url=(0017)http://localhost/ -->
<html>
<head>
	<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
	<title>Gppgle Map</title>
	<style type="text/css">
		html, body { height: 100%; margin:0px; }
		#mapCanvas { height: 100%; overflow:auto; }
	</style>
	<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
	<script type="text/javascript">
		//<![CDATA[
	    var map;
	    var geo;
	    google.maps.event.addDomListener(window, 'load', function () {
	        var mapdiv = document.getElementById("mapCanvas");
	        var myOptions = { zoom: 17,
	            center: new google.maps.LatLng(43.763781, 142.358084),
	            mapTypeId: google.maps.MapTypeId.ROADMAP,
	            scaleControl: true
	        };
	        map = new google.maps.Map(mapdiv, myOptions);
	        geo = new google.maps.Geocoder();
	    });

	    function moveMap(address) {
	        if (geo) {
	            geo.geocode({ 'address': address },
                    function (results, status) {
                        map.setCenter(results[0].geometry.location);
                    });
	        }
	    }
		//]]>
	</script>
</head>
<body>
    <div id="mapCanvas"></div>
</body>
</html>


コードが長すぎてポストできなかったので、SkyDriveにupしました。
参照設定とかもろもろについては、よしなにお願いします。

FSharp.Rx.zip



あわせて読みたい
@neueccさんのRxカテゴリ - neue.cc
http://neue.cc/category/programming/rx

なぜリアクティブプログラミングは重要か。- Conceptual Contexture
http://d.hatena.ne.jp/pokarim/20101226

やさしいFunctional reactive programming(概要編)- maoeのブログ
http://d.hatena.ne.jp/maoe/20100109/1263059731

Functional Reactive Programming - NyaRuRuの日記
http://d.hatena.ne.jp/NyaRuRu/20080317/p1

リアクティブプログラミングが世界中のソフトウェア技術者の95%に普及しないと考える理由と今後に対する期待 - おろかな日々
http://d.hatena.ne.jp/Rinta/20110103/p1

Rx + WCF RIA Services = 簡単?? via(非同期プログラミングは辛いよ) - かずきのBlog@Hatena
http://d.hatena.ne.jp/okazuki/20110113/1294922753



F#でRxしてFRPプログラマになってよ!

*1:具体的な説明でもよくわからない件

*2:RxはGUIのイベントにおいて強力な効果を発揮するため、Silverlight ToolkitやWindows Phone 7に含まれています。

*3:ニキビケア的な意味ではなく

*4:頭痛的な意味も含めて

*5:VB.NETでも普通に使えますが、ラムダ式がアレなので…つらいですね

*6:オーバーロード多すぎだろ…