ようこそ。睡眠不足なプログラマのチラ裏です。

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

いにしえからの言い伝えによると、よく訓練された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:オーバーロード多すぎだろ…

観たやつ

#460. 88ミニッツ
88(エイティーエイト)ミニッツ [DVD]
犯罪心理学者なアルパチーノです。
わずか88分の中で、さまざまな伏線が張り巡らされている。
クライマックスまで、より多くの人間を怪しく見せるのはうまいと思う。
ま、自分は推理できちゃいましたけどね…。


#461. 氷の微笑
氷の微笑 2 アンレイテッド・エディション [DVD]

いきなり高速で車ぶっとばしながらの…、冒頭からぶっ飛んでいます。
シャロン・ストーンが妖艶ですな。てゆうか、50過ぎてましたかw
ゴールデンラズベリー賞ラジー賞)』を、4部門もとった作品です。 すばらしい!w


#462. ザ・グリード
ザ・グリード [DVD]
豪華客船を舞台にした海洋モンスターパニック映画。かなりの回数テレビで放映されている。
出演俳優から漂うものなのかもしれないが、ほどよいB級加減がそそります。何回見てもたのしめる。


#463. アイスエイジ
アイス・エイジ 特別編 [DVD]
基本的に子供向けだが、大人でもたのしめる内容。普遍的な友情がテーマとなっている。
セリフのない、スクラット(古代リス)の存在も、ほどよいアクセントになっていてよい。
山寺宏一竹中直人はまぁわかるとして、爆笑問題の太田の声優ぶりがなかなかのもので、びっくり。
この手の作品は、日本語吹替えの方が好きかも。


#464. アイスエイジ2
アイス・エイジ2 特別編 [DVD]
今作も友情がテーマだが、ちょっとした恋愛要素も。
不器用なマニーと、自分をフクロネズミと信じて疑わないマンモスの女の子、クイーンのやりとりが見どころ。
あと、スクラット(古代リス)がどんぐりを追いかける姿は、やっぱりいいね。



#465. アイアンマン
アイアンマン [Blu-ray]
アイアンマン誕生秘話的なストーリーが大部分を占めている。
登場時の主人公はいけ好かない大金持ちなのだが、だんだん魅力的に思えてくるんだなー。
それも、秘書のペッパー・ポッツとのやり取りなんかから見て取れる人柄からか。
もはや当たり前のものとして受けいられているが、CG技術すごすぎだと思う。面白い。


#466. アイアンマン2
アイアンマン2  ブルーレイ&DVDセット [Blu-ray]
前作のラストで自らアイアンマンであることを告白した
大企業スターク・インダストリーのCEOのトニー・スタークのその後。
これから盛り上がっていくんだろうなという期待は、ちょっと裏切られたかも…。
前作がよかっただけに残念。3はどうかなー



#467. ナンバー23
ナンバー23 アンレイテッド・コレクターズ・エディション [DVD]
平凡な犬捕獲員がクリスマスに妻から贈られた一冊の古本に翻弄されて…。
歴史上の様々な凶事が、「23」という数字に深く関連している。
ジム・キャリーはコメディ以外でもよい演技ができますな。


#468. ウィッカーマン
ウィッカーマン [DVD]
30年以上前のカルト映画のリメイクらしい。
ニコラス・ケイジの映画ははずす気がしなかったのですが…。
どうやらこれは…、微妙でした。世界観はよいんだけどラストがあれではねえ。


#469. オーメン666
オーメン666 [DVD]
オーメンのリメイク作。ホラーとしては微妙だけど、カルトとしてはありかな。
ダミアン役の子役フィッツ・パトリックは、ちょっとインパクトに欠けるかな。


#470. ドラえもん のび太の人魚大海戦
映画ドラえもん のび太の人魚大海戦 スペシャル版 [DVD]
新ドラのやつ。まぁ無難。ハラハドキドキはないなー。
ジャイアンのいいやつ具合も微妙な感じだし。
それはそうと、しずかちゃん性格けっこうきついねw


#471. いぬのえいが
いぬのえいが プレミアム・エディション [DVD]
宮崎あおい出演につられてみると、若干痛い目を見るw
なんせ宮崎あおいは実質2,3分しか出ていないので…。
とはいえ、「いぬのえいが」としては、心温まるエピソードがありそれなりに楽しめる。
犬とは直接関係ないけど、パン屋の女の子が、絵のおかえしをしたシーンがよかったなw


#472. レニー・ハーリン・コベナント-幻魔降臨-
レニー・ハーリン コベナント 幻魔降臨 [DVD]
くそつまんないw


#473. ブラック会社に勤めてるんだが、もう俺は限界かもしれない
ブラック会社に勤めてるんだが、もう俺は限界かもしれない [DVD]
元ネタを読んだうえで見た。小池徹平くんがVB.NETで販売管理システムを構築していた。
描写が極端すぎる面はあるが、なかなか楽しめる。
この業界をこのように描いた映画は、国内だけではなく海外含めてもかなかかに貴重な存在。
森本レオが黒すぎてが笑いを誘う。


#474. 硝子の塔
硝子の塔 [DVD]
エロスな映画。シャロン・ストーン黒歴史の序章w
大量の監視カメラが話題になりましたなあ


#475. 幸せの1ページ
幸せの1ページ [DVD]
児童文学『Nim's Island』の映画化。
冒険作家アレックス・ローバー(アレクサンドラ)は冒険どころか引きこもりの対人恐怖症であった。 という設定がすべて。
前半は紹介、中盤はアクション、後半は感動でハッピーエンドという非常に分かりやすい構成。
子役のアビゲイル・ブレスリンは、他に「サイン」、「幸せのレシピ」、「私の中のあなた」などで見たことあるな。


#476. スカイ・クロラ
スカイ・クロラ [DVD]
原作は未読。世界観が独特だし、キャラクターも独特。
人を選ぶ作品だと思われるが、どこか冷たく退廃的な世界観には惹かれるものがあった。
主題歌の綾香「今夜も星に抱かれて…」もいい。


#477. ラスト・ブラッド
ラスト・ブラッド [DVD]
駄目なキルビル。え?


#478. 劇場版 NARUTO疾風伝 -ザ・ロストタワー-
イマイチだった。ナルトの活躍が目立つところはいいんだけども。
原作に合わせるかたちで、「ナルトのお父上がご登場」はファンサービスなんだろうけど、
プロットがお粗末で、盛りあがりに欠ける。お子様カカシ等との絡みも少ないのも×でしょう。
どう見ても手抜きな、土偶な風貌の敵キャラがそれに拍車をかけて・・・。
ジャンプで連載中の原作の方が100倍感動する展開になっていたので、
この劇場版は非常に残念と言わざるを得ませんでした。


#479. ディック&ジェーン 復讐は最高!
ディック&ジェーン [DVD]
グローバダイン社に勤務する優秀な社員のディック。
だが会社が倒産して生活に困ったディックは、ついに妻と強盗をするように。
そして自社株をすべて売り払って大儲けしていた元社長の資産4億ドルを奪い取る計画を立てる!
ジム・キャリージム・キャリーらしい役を演じているw ジム・キャリーが好きなら楽しめる作品。


#480. ホステル
ホステル 無修正版 コレクターズ・エディション [DVD]
かなりのグロ作品で、少々のエロもあり。
なので、見るばあいはそのあたり心づもりが必要。
しかしよくこんな映画つくったな。
あ、製作総指揮:クエンティン・タランティーノだったかw