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

オブジェクト指向は、シンプルできれいだが脆い。関数型プログラミングは、強く美しいが複雑。F#はいろいろな書き方が混ぜこぜになるよ。楽しいよ。

オブジェクト指向プログラミングが好きだ。関数型プログラミングが好きだ。


オブジェクト指向プログラミング

オブジェクト指向プログラミングは、コンテナの一般性を確保している点で優れている。
データと振る舞いをコンテナとしてまとめることで、プログラムをわかりやすく整理するという特徴がある。
特定のコンテナ自体を値として別のコンテナに収めたり、操作に対してパラメータ(引数)として受け渡すことでプログラムを表現する。
そのコンテナをオブジェクトって呼んでいる。


データと操作をオブジェクトとしてまとめる利点は、オブジェクトそのものに責務を定義できるからだ。
オブジェクトはそもそも自身の型を知っているので、オブジェクト内のデータによってその状態を識別できる。
カプセル化された実装によって、果たすべき機能が適切に表現される。ただし、オブジェクトで状態を扱うと
必ず副作用を巻き込んでしまうので「脆さ」が見え隠れする。そこで考え出されたのが不変オブジェクト。
不変オブジェクトの利用はオブジェクト指向プログラミングにおいて重要な要素のひとつだ。
オブジェクト指向プログラミングは、シンプルできれいだが脆いプログラミングスタイル。好きだ。


関数型プログラミング

関数型プログラミングは、関数を一人前の値(ファーストクラスオブジェクト)として扱える点で優れいている。
より高いレベルで操作を抽象化できるので新らたな制御構造を構築しやすい。
つまり、関数(計算)を連鎖させる(組み合わせる)ことで、あらゆる操作を柔軟に表現できる。
そこには本当の意味でのプログラムの再利用性と拡張性がある。関数型プログラミングの発想においては、操作はデータ(状態)を変更するのではなく、
入力を出力にマップ(写像)する考え方が基本となる。これ言うのは簡単で、やるのは少し頭をつかう。
いや、少しとは言い難い複雑さ難解さを伴うことも少なくないのが関数型だ。だがそれがいい。
モナドを勉強すると、関数型プログラミングを理解する大きな助けになる。時間はかかってもいい。勉強したい。


上手に関数を一般化できるようになると、表現力が格段に上がるので
簡潔で読みやすく美しいプログラムを書くことができる。副作用に対する心配が軽減されて、テスト容易性、保守性も抜群だ。
関数型プログラミングは、強く美しいが複雑なプログラミングスタイル。好きだ。


F#でオブジェクト指向プログラミングをすることはやぶさかではない。
F#は関数型言語だ。でもハイブリッドでイケてる言語なのでオブジェクト指向もできる。


F#は.NET Framework上にある関数型言語。実のところ関数だってその正体はオブジェクトだ。
.NET Framework上にあるので、容易にそのライブラリの恩恵を享受することができる。
VB.NETユーザーやC#ユーザーにとって、これはかなりうれしいアドバンテージだ。
現役.NETerが関数型言語を学ぶなら、何も迷う必要なんてない間違いなくF#だ。F#をおすすめする。


.NET Frameworkオブジェクト指向で実装されている。であるからして、
F#で利用する場合は、その恩恵を受けると同時に「状態」という名の副作用の相手をしなければならないことが往々にしてある。
多くの「状態」を扱うのであれば、オブジェクト指向を採用すればシンプルに表現することができる。
F#はそれができる。したがって、F#でオブジェクト指向プログラミングをすることはやぶさかではない。
VB.NETC#などで経験してきた、オブジェクト指向の設計パターンやアーキテクチャを再利用したいことがある。
F#はそれができる。したがって、最適解ではないかもしれないが、
F#でオブジェクト指向プログラミングをすることはやぶさかではない。


さまざまなトレードオフとの葛藤。混ぜこぜ楽しい
F#は関数型言語だよ。でもハイブリッドでイケてる言語なのでオブジェクト指向もできるよ。
じゃあF#でどう書く?


基本的には関数型プログラミングのスタイルで書くことが推奨される。なぜなら、強く美しいから。
でもね。.NET Frameworkとお友達である以上、いろいろと難しいことも出てくる。
F#でWPFSilverlightを扱おうとすると、大小含めていろいろな副作用と付き合う羽目になる。
脳が自然とオブジェクト指向したくなっちゃう*1。オブ脳発動は自然な流れ。これはもうある意味仕方がないことだ。
だからF#でプログラムを書くと、さまざまトレードオフと葛藤しながら、いろいろな書き方が混ぜこぜになるよ。
これはね。最初はどことなく気持ち悪さも感じたんだけど、今では結構しっくり来る。混ぜこぜ楽しい。


オブジェクト指向プログラミングしているコードは美しい」そう思っていた時期が俺にもありました。
まぁ間違いってわけじゃないんだけど、適切な表現か?というと今はなんか微妙に違う気がしている。
オブジェクト指向プログラミングってのは「美しい」というよりかは、「きれい」なんだよね。
整理整頓されているね的な意味で。関数型プログラミングしているコードは強く美しい。かっこいい。
もちろんアート的な意味でね。どちらもプログラミングに必要な要素だと思う。


まとめ
F#でプログラミングをすると、いろいろな書き方が混ぜこぜになるよ。
それは決して悪いことではないよ。F#によるマルチパラダイムなプログラミングはとても楽しいよ。


F#しようよ!

*1:今のところ、F#でオブジェクト指向プログラミングをするのは少し面倒なことも多いんだけど、言語仕様とIDEの強化などで少しずつ改善されていくと信じている。

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:オーバーロード多すぎだろ…

C#からF#のクロージャを利用するには、こんな風にしたらいんじゃないの的サンプル


 

実践 F# 関数型プログラミング入門

実践 F# 関数型プログラミング入門



まず、書籍のご紹介をさせていただきます。
荒井さん、いげ太さん共著の「実践 F# 関数型プログラミング入門 」が好評発売中です。
微力ながらレビュアーの一人としてご協力させていただきました。


基本的な文法からはじまり、関数型言語の特徴について丁寧に解説しています。
読者が新しい情報を順を追って少しずつ手に入れながら、徐々に成長していけるように配慮して構成されています。
F#に興味がある人はもちろん、はじめて関数型言語を勉強しようという方にもおすすめです!
お値段以上ニトリよろしくお買い得すぎるので、.NETerは全員買ったほうがいいと思います。まじで。



C#からF#のクロージャを利用するには

で、小ネタです。


「実践 F# 関数型プログラミング入門 」のコラム p331 F#と他の言語を組み合わせた開発を考える
に言及されているように、これからの.NET開発はF#とC#(VB.NET)を組み合わせた開発が
デファクトスタンダードとなっていくのだろうと思います(とてつもなく時間はかかりそうですが)。
実際、わたしは仕事でF#とC#を組み合わせたソフトウェア開発に今年から取り組んでいきます(やったね!)。
ということを考えた場合…、C#で作ったライブラリはF#から容易に呼び出したいし、F#で作ったライブラリはC#から容易に呼び出したいわけです。
そこで最もネックとなるのが、F#のクロージャを受け取る関数をC#から呼びだそうとする場合です。
System.Func<>は適切なFSharpFunc<_>型へ変換する必要があるからです。


「実践 F# 関数型プログラミング入門 」のコラム p317 C#からF#のクロージャを利用するには
に少し解説がありますが、具体的にはどうしたらよいのでしょうか。



以前書いた記事の中でわたしは、C#側からF#のクロージャを受け取る関数を呼び出すために以下のような関数をいくつか定義しました。

public static FSharpFunc<TArg1, TResult> ToFSharpFunc<TArg1, TResult>(this Func<TArg1, TResult> func)
{
    return FuncConvert.ToFSharpFunc(new Converter<TArg1, TResult>(func));
}

public static FSharpFunc<TArg,Unit> ToFSharpFunc<TArg>(this Action<TArg> action)
{
    return FuncConvert.ToFSharpFunc(action);
}


これらはいずれも引数が1つの場合に限られます。
引数が増えると、とんでもなく面倒くさいことになります以下のように。

public static FSharpFunc<TArg1, FSharpFunc<TArg2,TResult>> ToFSharpFunc<TArg1, TArg2, TResult>(this Func<TArg1,TArg2,TResult> func)
{
    Converter<TArg1, FSharpFunc<TArg2, TResult>> conv = value1 =>
        {
            return ToFSharpFunc<TArg2,TResult>(value2 => func(value1, value2));
        };
    return FSharpFunc<TArg1, FSharpFunc<TArg2, TResult>>.FromConverter(conv);
}


いやはや、実に「C#らしいコード(笑)」が好きな方が好きそうなコードですねw
T4などを用いるなどしてC#で実装するというのなら、別に止めたりはしませんが。
こんなにC#らしすぎるコードを大量に吐き出されても…、なんだかなーという気がします。
たぶん、F#で実装した方が素直です。


以下、F#側でSystem.Func<>からFSharpFunc<_>への変換を支援する関数の実装サンプルです。

namespace Microsoft.FSharp.Core
open System
open System.Linq.Expressions
open System.Runtime.CompilerServices 

[<Extension>]
module Util =
  type public CSharpFunc = 
    static member internal ToFSharpFunc<'a> (action:Action<'a>) = fun a -> action.Invoke(a)
    static member internal ToFSharpFunc<'a,'b> (action:Action<'a,'b>) = fun a b -> action.Invoke(a,b)
    static member internal ToFSharpFunc<'a,'b,'c> (action:Action<'a,'b,'c>) = fun a b c -> action.Invoke(a,b,c)
    static member internal ToFSharpFunc<'a,'b,'c,'d> (action:Action<'a,'b,'c,'d>) = fun a b c d -> action.Invoke(a,b,c,d)
    static member internal ToFSharpFunc<'a,'b,'c,'d,'e> (action:Action<'a,'b,'c,'d,'e>) = fun a b c d e -> action.Invoke(a,b,c,d,e)

    static member internal ToFSharpFunc<'a,'b> (func:Func<'a,'b>) = fun a -> func.Invoke(a)
    static member internal ToFSharpFunc<'a,'b,'c> (func:Func<'a,'b,'c>) = fun a b -> func.Invoke(a,b)
    static member internal ToFSharpFunc<'a,'b,'c,'d> (func:Func<'a,'b,'c,'d>) = fun a b c -> func.Invoke(a,b,c)
    static member internal ToFSharpFunc<'a,'b,'c,'d,'e> (func:Func<'a,'b,'c,'d,'e>) = fun a b c d -> func.Invoke(a,b,c,d)
    static member internal ToFSharpFunc<'a,'b,'c,'d,'e,'f> (func:Func<'a,'b,'c,'d,'e,'f>) = fun a b c d e -> func.Invoke(a,b,c,d,e)

  // Action -> FSharpFunc
  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let actionToFSharpFunc1<'TArg> (action:Action<'TArg>) = 
    action |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let actionToFSharpFunc2<'TArg1, 'TArg2> (action:Action<'TArg1, 'TArg2>) = 
    action |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let actionToFSharpFunc3<'TArg1, 'TArg2, 'TArg3> (action:Action<'TArg1, 'TArg2, 'TArg3>) = 
    action |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let actionToFSharpFunc4<'TArg1, 'TArg2, 'TArg3, 'TArg4> (action:Action<'TArg1, 'TArg2, 'TArg3, 'TArg4>) = 
    action |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let actionToFSharpFunc5<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5> (action:Action<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5>) = 
    action |> CSharpFunc.ToFSharpFunc

  // Expression<Action> -> FSharpFunc
  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionActionToFSharpFunc1<'TArg> (eaction:Expression<Action<'TArg>>) = 
    eaction.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionActionToFSharpFunc2<'TArg1, 'TArg2> (eaction:Expression<Action<'TArg1, 'TArg2>>) = 
    eaction.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionActionToFSharpFunc3<'TArg1, 'TArg2, 'TArg3> (eaction:Expression<Action<'TArg1, 'TArg2, 'TArg3>>) = 
    eaction.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionActionToFSharpFunc4<'TArg1, 'TArg2, 'TArg3, 'TArg4> (eaction:Expression<Action<'TArg1, 'TArg2, 'TArg3, 'TArg4>>) = 
    eaction.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionActionToFSharpFunc5<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5> (eaction:Expression<Action<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5>>) = 
    eaction.Compile() |> CSharpFunc.ToFSharpFunc

  // Func -> FSharpFunc
  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let funcToFSharpFunc1<'TArg, 'TResult> (func:Func<'TArg, 'TResult>) = 
    func |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let funcToFSharpFunc2<'TArg1, 'TArg2, 'TResult> (func:Func<'TArg1, 'TArg2, 'TResult>) = 
    func |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let funcToFSharpFunc3<'TArg1, 'TArg2, 'TArg3, 'TResult> (func:Func<'TArg1, 'TArg2, 'TArg3, 'TResult>) = 
    func |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let funcToFSharpFunc4<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TResult> (func:Func<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TResult>) = 
    func |> CSharpFunc.ToFSharpFunc 

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let funcToFSharpFunc5<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5, 'TResult> (func:Func<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5, 'TResult>) = 
    func |> CSharpFunc.ToFSharpFunc 

  // Expression<Func> -> FSharpFunc
  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionFuncToFSharpFunc1<'TArg,'TResult> (efunc:Expression<Func<'TArg, 'TResult>>) = 
    efunc.Compile() |> CSharpFunc.ToFSharpFunc 

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionFuncToFSharpFunc2<'TArg1, 'TArg2 ,'TResult> (efunc:Expression<Func<'TArg1, 'TArg2, 'TResult>>) = 
    efunc.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionFuncToFSharpFunc3<'TArg1, 'TArg2, 'TArg3 ,'TResult> (efunc:Expression<Func<'TArg1, 'TArg2, 'TArg3, 'TResult>>) = 
    efunc.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionFuncToFSharpFunc4<'TArg1, 'TArg2, 'TArg3, 'TArg4 ,'TResult> (efunc:Expression<Func<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TResult>>) = 
    efunc.Compile() |> CSharpFunc.ToFSharpFunc

  [<Extension>] 
  [<CompiledName("ToFSharpFunc")>]
  let expressionFuncToFSharpFunc5<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5 ,'TResult> (efunc:Expression<Func<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5, 'TResult>>) = 
    efunc.Compile() |> CSharpFunc.ToFSharpFunc

  // FSharpFunc -> Func
  [<Extension>] 
  [<CompiledName("ToFunc")>]
  let fsharpFuncToFunc1<'TArg, 'TResult,'c when 'c :> FSharpFunc<'TArg, 'TResult>> 
    (x: 'c ): ('TArg -> 'TResult) = 
    x.Invoke   

  [<Extension>] 
  [<CompiledName("ToFunc")>]
  let fsharpFuncToFunc2<'TArg1, 'TArg2, 'TResult,'c when 'c :> FSharpFunc<'TArg1, FSharpFunc<'TArg2, 'TResult>>> 
    (x: 'c ): ('TArg1 -> 'TArg2 -> 'TResult) = 
    fun a b -> x.Invoke(a).Invoke(b)

  [<Extension>] 
  [<CompiledName("ToFunc")>]
  let fsharpFuncToFunc3<'TArg1, 'TArg2, 'TArg3, 'TResult,'c when 'c :> FSharpFunc<'TArg1, FSharpFunc<'TArg2, FSharpFunc<'TArg3, 'TResult>>>> 
    (x: 'c ): ('TArg1 -> 'TArg2 -> 'TArg3 -> 'TResult) = 
    fun a b c -> x.Invoke(a).Invoke(b).Invoke(c)

  [<Extension>] 
  [<CompiledName("ToFunc")>]
  let fsharpFuncToFunc4<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TResult,'c when 'c :> FSharpFunc<'TArg1, FSharpFunc<'TArg2, FSharpFunc<'TArg3, FSharpFunc<'TArg4, 'TResult>>>>> 
    (x: 'c ): ('TArg1 -> 'TArg2 -> 'TArg3 -> 'TArg4 -> 'TResult) = 
    fun a b c d -> x.Invoke(a).Invoke(b).Invoke(c).Invoke(d)
 
  [<Extension>] 
  [<CompiledName("ToFunc")>]
  let fsharpFuncToFunc5<'TArg1, 'TArg2, 'TArg3, 'TArg4, 'TArg5, 'TResult,'c when 'c :> FSharpFunc<'TArg1, FSharpFunc<'TArg2, FSharpFunc<'TArg3, FSharpFunc<'TArg4, FSharpFunc<'TArg5, 'TResult>>>>>> 
    (x: 'c ): ('TArg1 -> 'TArg2 -> 'TArg3 -> 'TArg4 -> 'TArg5 -> 'TResult) = 
    fun a b c d e -> x.Invoke(a).Invoke(b).Invoke(c).Invoke(d).Invoke(e)

module Sample =

  let a1 a b = a b 
               b |> ignore
  let a2 a b c = a b c
                 c |> ignore
  let a3 a b c d = a b c d
                   d |> ignore
  let a4 a b c d e = a b c d e
                     e |> ignore
  let a5 a b c d e f = a b c d e f
                       f |> ignore

  let t1 a b = a b
  let t2 a b c = a b c
  let t3 a b c d = a b c d
  let t4 a b c d e = a b c d e
  let t5 a b c d e f = a b c d e f


まだまだ不足しているものがありますが、とりあえずということで。



一応、テスト駆動(TDD)でつくってみました

VS2010のテストで、C#からF#のクロージャを受け取る関数を呼び出して見ましょう。
拡張メソッドToFSharpFunc()だけでF#のクロージャに変換できるので、こりゃあ便利ですねー。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq.Expressions;
using Microsoft.FSharp.Core;

namespace TestProject1
{
    /// <summary>
    /// UnitTest1 の概要の説明
    /// </summary>
    [TestClass]
    public class UnitTest1
    {
        public UnitTest1() {}

        #region Action
        [TestMethod]
        public void a1()
        {
            var i = 0;
            Action<int> a = x => 
            {
                Console.WriteLine(x);
                i = 1;
            };

            Sample.a1(a.ToFSharpFunc(),3);
            Assert.AreEqual(i, 1);
        }

        [TestMethod]
        public void a2()
        {
            var i = 0;
            Action<int, int> a = (x,y) =>
            {
                Console.WriteLine("{0}:{1}",x,y);
                i = 1;
            };

            Sample.a2(a.ToFSharpFunc(),3,9);
            Assert.AreEqual(i, 1);
        }

        [TestMethod]
        public void a3()
        {
            var i = 0;
            Action<int, int, int> a = (x, y, z) =>
            {
                Console.WriteLine("{0}:{1}:{2}", x, y, z);
                i = 1;
            };

            Sample.a3(a.ToFSharpFunc(), 3, 9, 27);
            Assert.AreEqual(i, 1);
        }

        [TestMethod]
        public void a4()
        {
            var i = 0;
            Action<int, int, int, int> a = (x, y, z, b) =>
            {
                Console.WriteLine("{0}:{1}:{2}:{3}", x, y, z, b);
                i = 1;
            };

            Sample.a4(a.ToFSharpFunc(), 3, 9, 27, 12);
            Assert.AreEqual(i, 1);
        }

        [TestMethod]
        public void a5()
        {
            var i = 0;
            Action<int, int, int, int, int> a = (x, y, z, b, c) =>
            {
                Console.WriteLine("{0}:{1}:{2}:{3}:{4}", x, y, z, b, c);
                i = 1;
            };

            Sample.a5(a.ToFSharpFunc(), 3, 9, 27, 12,15);
            Assert.AreEqual(i, 1);
        }
        #endregion

        #region ExpressionAction
        [TestMethod]
        public void a1e()
        {
            Expression<Action<int>> a = x => 
                Console.WriteLine(x);
            Sample.a1(a.ToFSharpFunc(), 3);
        }

        [TestMethod]
        public void a2e()
        {
            Expression<Action<int, int>> a = (x, y) => 
                Console.WriteLine("{0}:{1}", x, y);
            Sample.a2(a.ToFSharpFunc(), 3, 9);
        }

        [TestMethod]
        public void a3e()
        {
            Expression<Action<int, int, int>> a = (x, y, z) =>
                Console.WriteLine("{0}:{1}:{2}", x, y, z);

            Sample.a3(a.ToFSharpFunc(), 3, 9, 27);
        }

        [TestMethod]
        public void a4e()
        {
            Expression<Action<int, int, int, int>> a = (x, y, z, b) =>
                Console.WriteLine("{0}:{1}:{2}:{3}", x, y, z, b);

            Sample.a4(a.ToFSharpFunc(), 3, 9, 27, 12);
        }

        [TestMethod]
        public void a5e()
        {
            Expression<Action<int, int, int, int, int>> a = (x, y, z, b, c) =>
                Console.WriteLine("{0}:{1}:{2}:{3}:{4}", x, y, z, b, c);

            Sample.a5(a.ToFSharpFunc(), 3, 9, 27, 12, 15);
        }
        #endregion

        #region Func
        [TestMethod]
        public void t1()
        {
            Func<int, int> f = x => x * 2;
            var result = Sample.t1(f.ToFSharpFunc(), 3);
            Assert.AreEqual(result,6);
        }

        [TestMethod]
        public void t2()
        {
            Func<int, int, int> f = (x, y) => x * 2 + y;
            var result = Sample.t2(f.ToFSharpFunc(), 3, 5);
            Assert.AreEqual(result, 11);
        }

        [TestMethod]
        public void t3()
        {
            Func<int, int, int, int> f = (x, y, z) => (x * 2 + y) / z;
            var result = Sample.t3(f.ToFSharpFunc(), 3, 6, 4);
            Assert.AreEqual(result, 3);
        }

        [TestMethod]
        public void t4()
        {
            Func<int, int, int, int, int> f = (x, y, z, a) => (x * 2 + y / z) % a;
            var result = Sample.t4(f.ToFSharpFunc(), 10, 8, 2, 5);
            Assert.AreEqual(result, 4);
        }

        [TestMethod]
        public void t5()
        {
            Func<int, int, int, int, int, int> f = (x, y, z, a, b) => (x + y + z + a) / b;
            var result = Sample.t5(f.ToFSharpFunc(), 3, 6, 4, 5, 2);
            Assert.AreEqual(result, 9);
        }
        #endregion

        #region ExpressionFunc 
        [TestMethod]
        public void t1e()
        {
            Expression<Func<int, int>> f = x => x * 2;
            var result = Sample.t1(f.ToFSharpFunc(), 3);
            Assert.AreEqual(result, 6);
        }


        [TestMethod]
        public void t2e()
        {
            Expression<Func<int, int, int>> f = (x, y) => x * 2 + y;
            var result = Sample.t2(f.ToFSharpFunc(), 3, 5);
            Assert.AreEqual(result, 11);
        }

        [TestMethod]
        public void t3e()
        {
            Expression<Func<int, int, int, int>> f = (x, y, z) => (x * 2 + y) / z;
            var result = Sample.t3(f.ToFSharpFunc(), 3, 6, 4);
            Assert.AreEqual(result, 3);
        }

        [TestMethod]
        public void t4e()
        {
            Expression<Func<int, int, int, int, int>> f = (x, y, z, a) => (x * 2 + y / z) % a;
            var result = Sample.t4(f.ToFSharpFunc(), 10, 8, 2, 5);
            Assert.AreEqual(result, 4);
        }

        [TestMethod]
        public void t5e()
        {
            Expression<Func<int, int, int, int, int, int>> f = (x, y, z, a, b) => (x + y + z + a) / b;
            var result = Sample.t5(f.ToFSharpFunc(), 3, 6, 4, 5, 2);
            Assert.AreEqual(result, 9);
        }
        #endregion

    }
}



ということで、今年はF#とC#の二刀流で頑張っていきまーす。

「Code Contractsで頻繁に使うのに、スニペット用意されてないじゃん!」とお嘆きの声に答えまして、とりあえず的に作ったC#のやつがあるので置いておきます。

以下が、「Code Contractsで頻繁に使うのに、スニペット用意されてないじゃん!」の追加の俺々コードスニペットたちです。
まぁ、なんで標準的に用意されていないのか、なんとなくわかるような気もしますけどね。
ちなみに、やつけ的にテキストエディタで作ったものです。ご注意ください。

name snippet filename explanation
EndContractBlock cecb contractendcontractblock.snippet メソッドのコントラクトに if-then-throw の形式で実行前の状態のみが含まれる場合に、コントラクト セクションの終わりをマークします。
EnsuresOnThrow(TException)(Boolean) ceot contractensuresonthrow.snippet 指定された例外および条件に基づいて、外側のメソッドまたはプロパティに実行後の状態のコントラクトを指定します。
Exists(T)(IEnumerable(T), Predicate(T)) cexe contractexistienumerablepredicate.snippet ある要素のコレクション内の要素が関数内に存在するかどうかを判断します。
Exists(Int32, Int32, Predicate(Int32)) cexi contractexistintintpredicate.snippet ある整数範囲内のすべての整数について指定したテストが true かどうかを判定します。
ForAll(T)(IEnumerable(T), Predicate(T)) cfalle contractforallienumerablepredicate.snippet コレクション内のすべての要素が関数内に存在するかどうかを判断します。
ForAll(Int32, Int32, Predicate(Int32)) cfalli contractforallintintpredicate.snippet 指定した範囲内のすべての整数について特定の状態が有効かどうかを判定します。


EndContractBlock

レガシーコード体質改善の際などを中心として、これはかなり活躍するはずです。
その他の場合でも、Contractの明示的なエンドポイントとして積極的に使っていってもいいと思います。


contractendcontractblock.snippet

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <Title>Contract.EndContractBlock</Title>
      <Shortcut>cecb</Shortcut>
      <Description>Emits a 'Contract.EndContractBlock' clause</Description>
      <Author>zecl</Author>
    </Header>
    <Snippet>
      <Imports>
        <Import>
          <Namespace>System.Diagnostics.Contracts</Namespace>
        </Import>
      </Imports>
      <Declarations>
        <Object Editable="false">
          <ToolTip>メソッドのコントラクトに if-then-throw の形式で実行前の状態のみが含まれる場合に、コントラクト セクションの終わりをマークします。</ToolTip>
          <Function>
          </Function>
        </Object>
      </Declarations>
      <Code Language="CSharp" Kind="any"><![CDATA[Contract.EndContractBlock();$end$]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>


EnsuresOnThrow(TException)(Boolean)

奥さん、例外発生時の実行後の状態のコントラクトを指定できるんですってよ。
ぶっちゃけこれ超重要だと思うんですが。


contractensuresonthrow.snippet

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>Contract.EnsuresOnThrow&lt;E&gt;</Title>
      <Shortcut>ceot</Shortcut>
      <Description>Emits a 'Contract.EnsuresOnThrow&lt;Exc&gt;' clause</Description>
      <Author>zecl</Author>
    </Header>
    <Snippet>
      <Imports>
        <Import>
          <Namespace>System.Diagnostics.Contracts</Namespace>
        </Import>
      </Imports>
      <Declarations>
        <Object Editable="true">
          <ID>exception</ID>
          <ToolTip>実行後の状態のチェックを起動する例外の種類。</ToolTip>
          <Default>TException</Default>
          <Function>
          </Function>
        </Object>
        <Object Editable="true">
          <ID>condition</ID>
          <ToolTip>テストする条件式。</ToolTip>
          <Default>false</Default>
          <Function>
          </Function>
        </Object>
      </Declarations>
      <Code Language="CSharp" Kind="method body"><![CDATA[Contract.EnsuresOnThrow<$exception$>($condition$);$end$]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>


Exists(T)(IEnumerable(T), Predicate(T))

"LINQバンザイ"、"LINQ最高"のご時世ですから。
こういうタイプの契約を指定したくなることもあると思うんです。思っちゃうんです。


contractexistienumerablepredicate.snippet

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>Contract.Exists&lt;T&gt;</Title>
      <Shortcut>cexe</Shortcut>
      <Description>Emits a 'Contract.Exists&lt;T&gt;' clause</Description>
      <Author>zecl</Author>
    </Header>
    <Snippet>
      <Imports>
        <Import>
          <Namespace>System.Diagnostics.Contracts</Namespace>
        </Import>
      </Imports>
      <Declarations>
        <Object Editable="true">
          <ID>type</ID>
          <ToolTip>collection に格納されている型。</ToolTip>
          <Default>int</Default>
          <Function>
          </Function>
        </Object>
        <Object Editable="true">
          <ID>collection</ID>
          <ToolTip>predicate に渡される型 T の要素が含まれているコレクション。</ToolTip>
          <Default>new int[] {}</Default>
          <Function>
          </Function>
        </Object>
        <Object Editable="true">
          <ID>predicate</ID>
          <ToolTip>collection 内の要素を評価する関数。</ToolTip>
          <Default>x => true</Default>
          <Function>
          </Function>
        </Object>
      </Declarations>
      <Code Language="CSharp" Kind="method body"><![CDATA[Contract.Exists<$type$>($collection$, $predicate$);$end$]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>


Exists(Int32, Int32, Predicate(Int32))

こっちもね。

contractexistintintpredicate.snippet

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>Contract.Exists</Title>
      <Shortcut>cexi</Shortcut>
      <Description>Emits a 'Contract.Exists' clause</Description>
      <Author>zecl</Author>
    </Header>
    <Snippet>
      <Imports>
        <Import>
          <Namespace>System.Diagnostics.Contracts</Namespace>
        </Import>
      </Imports>
      <Declarations>
        <Object Editable="true">
          <ID>fromInclusive</ID>
          <ToolTip>predicate に渡す最初の整数。</ToolTip>
          <Default>0</Default>
          <Function>
          </Function>
        </Object>
        <Object Editable="true">
          <ID>toExclusive</ID>
          <ToolTip>predicate に渡す最後の整数より 1 だけ大きい数。</ToolTip>
          <Default>1</Default>
          <Function>
          </Function>
        </Object>
        <Object Editable="true">
          <ID>predicate</ID>
          <ToolTip>指定した範囲内の任意の整数値を評価する関数。</ToolTip>
          <Default>x => true</Default>
          <Function>
          </Function>
        </Object>
      </Declarations>
      <Code Language="CSharp" Kind="method body"><![CDATA[Contract.Exists($fromInclusive$, $toExclusive$, $predicate$);$end$]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>


ForAll(T)(IEnumerable(T), Predicate(T))

そう、ループで回している最中の契約だって結びたいんです。Eiffelのバートランド・メイヤー大先生もそうおっしゃっております。
あと、名著「達人プログラマー」にもいろいろ書いてあったような気がしますよ。


contractforallienumerablepredicate.snippet

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>Contract.ForAll&lt;T&gt;</Title>
      <Shortcut>cfalle</Shortcut>
      <Description>Emits a 'Contract.ForAll&lt;T&gt;' clause</Description>
      <Author>zecl</Author>
    </Header>
    <Snippet>
      <Imports>
        <Import>
          <Namespace>System.Diagnostics.Contracts</Namespace>
        </Import>
      </Imports>
      <Declarations>
        <Object Editable="true">
          <ID>type</ID>
          <ToolTip>collection に格納されている型。</ToolTip>
          <Default>int</Default>
          <Function>
          </Function>
        </Object>
        <Object Editable="true">
          <ID>collection</ID>
          <ToolTip>predicate に渡される型 T の要素が含まれているコレクション。</ToolTip>
          <Default>new int[] {}</Default>
          <Function>
          </Function>
        </Object>
        <Object Editable="true">
          <ID>predicate</ID>
          <ToolTip>collection 内の要素を評価する関数。</ToolTip>
          <Default>x => true</Default>
          <Function>
          </Function>
        </Object>
      </Declarations>
      <Code Language="CSharp" Kind="method body"><![CDATA[Contract.ForAll<$type$>($collection$, $predicate$);$end$]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>


ForAll(Int32, Int32, Predicate(Int32))

こっちもね。


contractforallintintpredicate.snippet

<?xml version="1.0" encoding="utf-8"?>
<CodeSnippets xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
  <CodeSnippet Format="1.0.0">
    <Header>
      <SnippetTypes>
        <SnippetType>Expansion</SnippetType>
      </SnippetTypes>
      <Title>Contract.ForAll</Title>
      <Shortcut>cfalli</Shortcut>
      <Description>Emits a 'Contract.ForAll' clause</Description>
      <Author>zecl</Author>
    </Header>
    <Snippet>
      <Imports>
        <Import>
          <Namespace>System.Diagnostics.Contracts</Namespace>
        </Import>
      </Imports>
      <Declarations>
        <Object Editable="true">
          <ID>fromInclusive</ID>
          <ToolTip>predicate に渡す最初の整数。</ToolTip>
          <Default>0</Default>
          <Function>
          </Function>
        </Object>
        <Object Editable="true">
          <ID>toExclusive</ID>
          <ToolTip>predicate に渡す最後の整数より 1 だけ大きい数。</ToolTip>
          <Default>1</Default>
          <Function>
          </Function>
        </Object>
        <Object Editable="true">
          <ID>predicate</ID>
          <ToolTip>指定した範囲内の任意の整数値を評価する関数。</ToolTip>
          <Default>x => true</Default>
          <Function>
          </Function>
        </Object>
      </Declarations>
      <Code Language="CSharp" Kind="method body"><![CDATA[Contract.ForAll($fromInclusive$, $toExclusive$, $predicate$);$end$]]></Code>
    </Snippet>
  </CodeSnippet>
</CodeSnippets>

というわけで、クオリティはともかくとして月に1回は更新しようという自分ルール守った。うん。

カスタム ビジュアライザって「刺身たんぽぽ」になりがちだよね。楽にカスタム ビジュアライザを量産して「たのしいデバッグ」を。


デバッガ ビジュアライザとは

デバッガ ビジュアライザとは、Visual Studioデバッグをしている時に、
オブジェクトの状態を照会することが出来る画面および、その機能のことです。
そうです。いつもお世話になっているアレです。たとえば、string型についてデフォルトで提供されている
テキストビジュアライザやXMLビジュアライザ、HTMLビジュアライザなどがそれにあたります。

 



デバッガ ビジュアライザのアーキテクチャには、デバッガ側とデバッグ対象側の2つがあって、
1つはデバッガ側ドメインで、もう1つはVisual Studio デバッグ側のドメインで動作し、互いに協調して動作します。


カスタム ビジュアライザとは

Visual Studio 2005以降のデバッガは非常に強力で、
ただ強力なだけではなく自らがデバッガの機能を拡張することが可能となっています。
その拡張の1つが、カスタム ビジュアライザです。
使いやすいカスタム ビジュアライザを準備しておけば、デバッグの効率が飛躍的に向上します。


カスタム ビジュアライザの基本的な作成方法については、「チュートリアル : C# でビジュアライザを記述する」にて解説があります。


カスタム ビジュアライザの対象にできる型に関する注意

カスタム ビジュアライザでは、Object型 および Array型 を除く任意のマネージ クラスについて作成可能です。
また、ジェネリック型のサポートには制限があり、ジェネリック型がオープン構築型の場合にのみに限られます。
クローズ構築型のジェネリックはサポートされません。


カスタム ビジュアライザの対象にできる型には、更に把握しておくべき条件があります。
ビジュアライザは前述したようにデバッガ側とデバッグ対象側の2つのアーキテクチャによって動作します。
これは、それぞれ異なる世界です。.NETの言葉で言えば、異なるアプリケーションドメインで動作していると言います。
つまり、ビジュアライザの対象とされるオブジェクトは、デバッグ対象側からデバッガ側へと
異なるアプリケーションドメイン間で通信されることを意味しています。
ですから、ビジュアライザの対象にできるオブジェクトの型は MarshalByRefObject から派生しているか、
あるいは Serializable属性がマークされていてシリアル化可能である必要があるというわけです。


では、ビジュアライザの対象としたいオブジェクトが MarshalByRefObject の派生型でもなく、
シリアライズ可能でもなかった場合はビジュアライザが使えないことになります。どうすればよいでしょう。
そのような場合は、オブジェクトを構成するのに最低限必要な情報から構成した、
シリアル化可能な(Serializable属性でマークされた)プロキシクラスを用意します。
VisualizerObjectSourceの派生型を定義し、GetData(object target, Stream outgoingData)をオーバーライドし、
プロキシクラスをシリアル化するよう実装します。つまり異なるドメインについてプロキシを通じてやり取りするようにします。


カスタム ビジュアライザの作成は「刺身たんぽぽ」になりがち

基本的な作成方法について理解し、いくつかのカスタム ビジュアライザを作成すると、
いくつも似たような実装を繰り返していることにすぐに気が付くでしょう。
型について異なるだけで、似たような実装がたくさん現れます。ちょっと待って。これって「刺身たんぽぽ」じゃね?
「いいじゃんいいじゃん。別にコピペすりゃあ済む話。」、「ぼく刺身たんぽぽが天職ですからッ!(キリッ」
いやいやいやいや、それないわーーー!そんなこと言わないでジェネリック活用してください。お願いします。


ということで、ジェネリックを活用した実装サンプルを以下に示します。



ジェネリックを活用したDebuggerVisualizer作成サンプル

 


DialogDebuggerVisualizerのジェネリック表現と、VisualizerObjectSourceのジェネリック表現です。
この実装だけを見ても、いまいちピンとこないと思います。他のコードと合わせて見てください。

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Microsoft.VisualStudio.DebuggerVisualizers;

namespace DebuggerVisualizers
{
    public class DialogDebuggerVisualizer<TTarget, TVisualizer> : DialogDebuggerVisualizer
        where TVisualizer : DialogDebuggerVisualizer<TTarget, TVisualizer>
    {
        protected override void Show(IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            var obj = (TTarget)objectProvider.GetObject();
            obj.Show<TTarget, TVisualizer>(windowService, objectProvider);
        }
    }

    public class VisualizerObjectSourceProxy<TTarget, TProxy> : VisualizerObjectSource
    {
        public override void GetData(object target, Stream outgoingData)
        {
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(outgoingData, ((TTarget)target).CreateProxy<TTarget,TProxy>());
        }
    }
}


「ビジュアライザの対象とする型」と、「カスタム ビジュアライザの型」ごとについての動作に関する定義などなど

using System;
using System.Security.Cryptography;
using Microsoft.VisualStudio.DebuggerVisualizers;

namespace DebuggerVisualizers
{
    public static class DebuggerVisualizerExtentions
    {
        private class ShowTypeSelector<TTarget,TVisualizer>
        {
            public static Action<TTarget, IDialogVisualizerService, IVisualizerObjectProvider> debuggerVisualizerShow = (obj, ws, op) => { };

            static ShowTypeSelector()
            {
                ShowTypeSelector<TTarget,TVisualizer>.debuggerVisualizerShow = (obj, ws, op) => { };
                ShowTypeSelector<Uri,UriVisualizer>.debuggerVisualizerShow = (obj, ws, op) => obj.DebuggerVisualizerUriShow(ws, op);
                ShowTypeSelector<Uri,UriPropertyGridVisualizer>.debuggerVisualizerShow = (obj, ws, op) => obj.DebuggerVisualizerPropertyGridShow(ws, op);
                ShowTypeSelector<String,StringUriVisualizer>.debuggerVisualizerShow = (obj, ws, op) => obj.DebuggerVisualizerShow(ws, op);
                ShowTypeSelector<String, StringPropertyGridVisualizer>.debuggerVisualizerShow = (obj, ws, op) => obj.DebuggerVisualizerPropertyGridShow(ws, op);
                ShowTypeSelector<RijndaelManagedProxy, RijndaelManagedPropertyGridVisualizer>.debuggerVisualizerShow = (obj, ws, op) => obj.DebuggerVisualizerPropertyGridShow(ws, op);
            }
        }

        public static void Show<TTarget,TVisualizer>(this TTarget self, IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            ShowTypeSelector<TTarget,TVisualizer>.debuggerVisualizerShow(self, windowService, objectProvider);
        }

        private class CreateTypeSelector<TTarget, TProxy>
        {
            public static Func<TTarget, TProxy> createProxy = target => { return default(TProxy); };

            static CreateTypeSelector()
            {
                CreateTypeSelector<TTarget, TProxy>.createProxy = target => { return default(TProxy); };
                CreateTypeSelector<RijndaelManaged, RijndaelManagedProxy>.createProxy = target => { return new RijndaelManagedProxy(target); };
            }
        }

        public static TProxy CreateProxy<TTarget, TProxy>(this TTarget target)
        {
            return CreateTypeSelector<TTarget, TProxy>.createProxy(target);
        }
    }
}

各DialogDebuggerVisualizerです。

using System;

namespace DebuggerVisualizers
{
    public class UriVisualizer : DialogDebuggerVisualizer<Uri,UriVisualizer> { }
    public class UriPropertyGridVisualizer : DialogDebuggerVisualizer<Uri, UriPropertyGridVisualizer> { }
    public class StringUriVisualizer : DialogDebuggerVisualizer<String, StringUriVisualizer> { }
    public class StringPropertyGridVisualizer : DialogDebuggerVisualizer<String, StringPropertyGridVisualizer> { }
    public class IntVisualizer : DialogDebuggerVisualizer<int, IntVisualizer> { }

    #region Proxy
    public class RijndaelManagedPropertyGridVisualizer : DialogDebuggerVisualizer<RijndaelManagedProxy, RijndaelManagedPropertyGridVisualizer> { }
    #endregion
}

VisualizerObjectSourceです。

using System.Security.Cryptography;

namespace DebuggerVisualizers
{
    public class RijndaelManagedVisualizerObjectSource : VisualizerObjectSourceProxy<RijndaelManaged, RijndaelManagedProxy> { }
}


カスタム ビジュアライザ対象の型であるUriについての、ビジュアライザ表示方法の具体的な実装です。
UriをWebBrowserで表示するDebuggerVisualizerUriShowと、PropertyGridで表示するDebuggerVisualizerPropertyGridShowを
拡張メソッドで実装しています。objectProvider.ReplaceObjectによって、
ビジュアライザ対象オブジェクトの値を書き換えることができます。

using System;
using Microsoft.VisualStudio.DebuggerVisualizers;

namespace DebuggerVisualizers
{
    public static class UriExtentions
    {
        public static void DebuggerVisualizerUriShow(this Uri self, IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            using (var form = new UriVisualizerForm())
            {
                form.webBrowser1.Navigate(self);
                windowService.ShowDialog(form);

                if (form.DialogResult != System.Windows.Forms.DialogResult.OK)
                    return;
                if (!objectProvider.IsObjectReplaceable)
                    return;

                objectProvider.ReplaceObject(new Uri(form.textBox1.Text));
            }
        }

        public static void DebuggerVisualizerPropertyGridShow(this Uri self, IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            using (var form = new PropertyGridVisualizer())
            {
                form.propertyGrid1.SelectedObject = self;
                windowService.ShowDialog(form);
            }
        }
    }
}


カスタム ビジュアライザ対象の型であるstringについての、ビジュアライザ表示方法の具体的な実装です。

using System;
using Microsoft.VisualStudio.DebuggerVisualizers;

namespace DebuggerVisualizers
{
    public static class StringExtentions
    {
        public static void DebuggerVisualizerShow(this string self, IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            using (var form = new UriVisualizerForm())
            {
                form.webBrowser1.Navigate(new Uri(self));
                windowService.ShowDialog(form);

                if (form.DialogResult != System.Windows.Forms.DialogResult.OK)
                    return;
                if (!objectProvider.IsObjectReplaceable)
                    return;

                objectProvider.ReplaceObject(form.textBox1.Text);
            }
        }

        public static void DebuggerVisualizerPropertyGridShow(this string self, IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            using (var form = new PropertyGridVisualizer())
            {
                form.propertyGrid1.SelectedObject = self;
                windowService.ShowDialog(form);
            }
        }
    }
}


RijndaelManagedはシリアル化不可であるため、シリアル化可能なRijndaelManagedProxyというプロキシクラスを作ります。

using System;
using System.Security.Cryptography;

namespace DebuggerVisualizers
{
    [Serializable]
    public class RijndaelManagedProxy
    {
        public byte[] IV { get; private set; }
        public byte[] Key { get; private set; }

        public RijndaelManagedProxy(RijndaelManaged target)
        {
            IV = target.IV;
            Key = target.Key;
        }
    }
}

カスタム ビジュアライザ対象の型であるRijndaelManagedProxyについての、ビジュアライザ表示方法の具体的な実装です。

using System;
using System.Windows.Forms;
using Microsoft.VisualStudio.DebuggerVisualizers;

namespace DebuggerVisualizers
{
    public static class RijndaelManagedExtentions
    {
        public static void DebuggerVisualizerPropertyGridShow(this RijndaelManagedProxy self, IDialogVisualizerService windowService, IVisualizerObjectProvider objectProvider)
        {
            using (var form = new PropertyGridVisualizer())
            {
                form.propertyGrid1.SelectedObject = self;
                windowService.ShowDialog(form);
            }
        }
    }
}


WebBrowser表示するビジュアライザフォームです。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace DebuggerVisualizers
{
    public partial class UriVisualizerForm : Form
    {
        public UriVisualizerForm()
        {
            InitializeComponent();
            this.Text = "Uri Visualizer";
            this.StartPosition = FormStartPosition.CenterScreen;
        }

        private void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
        {
            if (webBrowser1.Url == null) return;
            textBox1.Text = webBrowser1.Url.ToString();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            this.DialogResult = System.Windows.Forms.DialogResult.OK;
        }
    }
}


PropertyGrid表示するビジュアライザフォームです。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace DebuggerVisualizers
{
    public partial class PropertyGridVisualizer : Form
    {
        public PropertyGridVisualizer()
        {
            InitializeComponent();
            this.Text = "PropertyGrid ビジュアライザー";
            this.StartPosition = FormStartPosition.CenterScreen;
        }
    }
}

assemblyファイルに以下を記述し、DebuggerVisualizerを宣言します。
「ビジュアライザー」としているのはワザトです。

/// DebuggerVisualizer
[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DebuggerVisualizers.UriVisualizer),
    Target = typeof(System.Uri),
    Description = "Uri ビジュアライザー"
)]

[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DebuggerVisualizers.UriPropertyGridVisualizer),
    Target = typeof(System.Uri),
    Description = "PropertyGrid ビジュアライザー"
)]

[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DebuggerVisualizers.StringUriVisualizer),
    Target = typeof(System.String),
    Description = "Uri ビジュアライザー"
)]

[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DebuggerVisualizers.StringPropertyGridVisualizer),
    Target = typeof(System.String),
    Description = "PropertyGrid ビジュアライザー"
)]

[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DebuggerVisualizers.RijndaelManagedPropertyGridVisualizer),
    typeof(DebuggerVisualizers.RijndaelManagedVisualizerObjectSource),
    Target = typeof(System.Security.Cryptography.RijndaelManaged),
    Description = "RijndaelManaged ビジュアライザー"
)]

[assembly: System.Diagnostics.DebuggerVisualizer(
    typeof(DebuggerVisualizers.IntVisualizer),
    Target = typeof(int),
    Description = "Int Visualizer(未実装)"
)]

ビジュアライザのテスト
VS2010の単体テストで。


ビジュアライザのテストには、VisualizerDevelopmentHostクラスを用いて行ないます。

using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.VisualStudio.DebuggerVisualizers;
using DebuggerVisualizers;

namespace TestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestShowUriVisualizer()
        {
            var str = "http://www.bing.com/";
            var uri = new Uri(str);
            ShowVisualizer(uri, typeof(UriVisualizer));
            ShowVisualizer(uri, typeof(UriPropertyGridVisualizer));
        }

        [TestMethod]
        public void TestShowStringUriVisualizer()
        {
            var str = "http://www.bing.com/";
            ShowVisualizer(str, typeof(StringUriVisualizer));
            ShowVisualizer(str, typeof(StringPropertyGridVisualizer));
        }

        [TestMethod]
        public void TestShowRijendaeManagedVisualizer()
        {
            var rijndael = new RijndaelManaged();
            ShowRijendaeManagedVisualizer(rijndael
                , typeof(RijndaelManagedPropertyGridVisualizer)
                , typeof(RijndaelManagedVisualizerObjectSource));
        }

        [TestMethod]
        public void TestShowIntVisualizer()
        {
            var i = 100;
            ShowVisualizer(i, typeof(IntVisualizer));
        }

        public static void ShowVisualizer(object objectToVisualize,Type visualizer)
        {
            var visualizerHost = new VisualizerDevelopmentHost(objectToVisualize, visualizer);
            visualizerHost.ShowVisualizer();
        }

        public static void ShowRijendaeManagedVisualizer(object objectToVisualize,Type visualizer,Type visualizerObjectSource)
        {
            var visualizerHost = new VisualizerDevelopmentHost(objectToVisualize, visualizer, visualizerObjectSource);
            visualizerHost.ShowVisualizer();
        }
    }
}


というような感じで実装しておけば、きっと「刺身たんぽぽ」の呪いから開放されることでしょう。
カスタム ビジュアライザ大好きです。どんどん作ってどんどん有効活用して良きデバッグライフを。
VB.NET版とF#版も書こうかと思いましたが、力尽きました。何かの参考になれば幸いです。

実はオブジェクト指向ってしっくりきすぎるんです! 不変オブジェクトのすゝめ。

バグのないソフトウェアを作りたい

お仕事では主にVB.NETC#を。趣味のプログラミングでは関数型言語F#を利用しています。
私自身のF#スキル(関数型的な考え方)は、まだまだ実践レベルとはとても言えないシロモノだけど、
面白い発見と多くの可能性を感じられる言語なので、F#はさわっていてとても楽しい。


私はこれまでオブジェクト指向言語によるオブジェクト指向プログラミングをこよなく愛してきました。
というのも、「いかにバグを減らすか」、「バグのないソフトウェアを作ること」が私の最大の関心事だからです。
バグの多いコード、あるいは技術的負債の多いコードというのは、コスト的な問題があるばかりか、
開発者の身体や心までもを不健康にし、われわれに大きな不幸をもたらすことを経験的にわかっているからです。
わたしにとってオブジェクト指向技術は、それらの問題を防いだり解決をする手段として適した技術でした。
そしてまた、私が関数型言語を学ぶ理由も、やはりバグのないソフトウェアを作りたいからに他なりません。
健康でありたい。幸せでありたい。そして美しいコードが好きだからです。


関数型言語の小さいようで小さくない、とても大きなウマみ

関数型言語に初めて触れたのはHaskellでした。もう2年以上前になります。


.NET Framework に LINQ が登場し、C#ラムダ式を少しずつ利用するようになった頃、
「本物のプログラマHaskellを使う」という*1記事を読んだのがきっかけでした。
私が知るHaskellで最も特徴的なところと言えば、「遅延評価」およびIOがファーストクラスであること。
あるいはモナドの存在ですが、Haskellを知ってなにより私の目を引いたのは、
「状態の変化(あるいは再代入)という副作用が存在しないので無用なバグを未然に防げる」というところです。
これはHaskell特有のものというわけでもなく、一般的な関数型言語に備わっています。


JavaVB.NETC#等のオブジェクト指向言語では、ValueObject(不変オブジェクト)を実装しなければ実現できなかったことが、
関数型言語では、強力で且つ簡単にimmutable表現が可能になることに感動しました。
また、これは関数の再利用性の高さを意味していました。
「これを使えば些細な副作用にびくびくすることがなくなるぞ!すばらしい!」
というのが、私が最初に感じた、関数型言語の小さいようで小さくない、とても大きなウマみです。
バグの多いソフトウェアというのは往々にして副作用の多いプログラムなので、
副作用の少なさを促進することはとても魅力です。


関数プログラミングオブジェクト指向の長所を組み合わせたプログラミングスタイルを

F#を書くときは、なるべく関数プログラミングな実装を意識しています。
でもふと気が付くと、オブジェクト指向な頭に切り替わっている自分がいます。


私がまだ関数型に不慣れなせいもあるのか、オブジェクト指向では当たり前に表現できることが関数型では難しく感じます。
その表現方法(発想をすること)が難しい。というのももちろんありますが、
仮に関数型な書き方をひらめいたとしても、可読性や拡張性、可用性などの面で長期的に考えると、
"関数型よりもオブジェクト指向での表現を選択した方がよい"と感じる場面があります。
関数型言語で書いていても「自分でクラスを作ってオブジェクト指向っぽいことをしたくなる。」ことのなんと多いことか!
関数型言語およびその考え方は、これからのソフトウェア開発では間違いなく必要とされてくる必須技術と感じているけど、
やはりオブジェクト指向もまだまだ手放せません。オブジェクト指向はプログラミングの基本なんです。
いい具合に枯れているんです。


そう。オブジェクト指向ってしっくりきすぎるんです!


あなたのオブジェクト指向には、ValueObject(不変オブジェクト)はありますか?

ところで、あなたのオブジェクト指向には、ValueObject(不変オブジェクト)はありますか?
もしこれを知らない。あるいはその利用が極端に少ないと言うのであれば、
あなたは、オブジェクト指向プログラミングをまだまだ実践出来ていない可能性が高いでしょう。



ThoughtWorksアンソロジー
ThoughtWorksアンソロジー ―アジャイルとオブジェクト指向によるソフトウェアイノベーション


ThoughtWorksアンソロジーで提案されている「オブジェクト指向エクササイズ9つのルール」の中に、
「すべてのプリミティブ型と文字列型をラップすること」というルールがあります。
現実的に、プリミティブ型と文字列型そのすべてをラップすることは難しいと感じられるかもしれません。
実際、ソフトウェアの規模が大きくなるほどに難しいでしょう。これはなかなか刺激的なルールです。
すべては無理でも、特に重要と感じられる部分に導入するだけでも、
ValueObject(不変オブジェクト)はとても大きな効果を発揮します。
設計、実装、運用、保守などあらゆる面で格段に有利になります。


必ず「不変オブジェクトとして存在するよう設計できないか」を検討する
不変オブジェクトとは、生成されて以降は値および状態が変化することのないオブジェクトです。


不変オブジェクトを導入することは、状態変化のある可変オブジェクトを主体とするよりもいくつかの利点があります。
まず、不変オブジェクトは状態が変更されないのでシンプルです。そのオブジェクトを使用する開発者は、
内部状態の遷移を意識する必要がありません。つまり、プログラムの副作用を減らす効果があります。
単純なヒューマンエラーを減少させて、無用なバグの根絶に一役買います。
また、不変オブジェクトはスレッドセーフであり(複数のスレッドからの同時アクセスによりデータ矛盾が発生しない)、
データの複製(クローン)を考える必要がなくなります。キャッシュとして複数クライアントによるデータ共有も可能です。
もちろん、オブジェクトの生存期間の途中でハッシュ値が変わるとマズイというようなシチュエーションでも有効です。


不変オブジェクトには様々なメリットがあります。ですからオブジェクト指向でクラスを設計する際には、
まずそのクラスのインスタンスが「不変オブジェクトとして存在するよう設計できないか」を検討する必要があります。
理由があって可変オブジェクトとして設計する場合は、その可変部分の対象を出来る限り狭く設計するように心がけます。


不変オブジェクトと可変オブジェクトのパフォーマンスに関するトレードオフ

不変オブジェクトを利用すると可変オブジェクトを利用するに比べて
パフォーマンスが低下してしまう。などと言う神話がありますが、それは少し違います。


不変オブジェクトを用いて状態の変更を表現する場合は、そのオブジェクトそのものの状態は変更せずに、
新たに不変オブジェクトを生成する方法をとります。これは、状態変化が可能な可変オブジェクトであれば
生成する必要のないオブジェクトを新しく生成するため、高コストになってしまうのではないかというのが神話の起源です。

しかし、プログラム内でのオブジェクトの使用方法によっては、実際には不変オブジェクトを利用した方がパフォーマンス的にメリットがある場合も少なくありません。
例えば、不変オブジェクトはオブジェクトを防御的にコピーする(クローンの)必要がないため。可変オブジェクトに比べてパフォーマンスが期待できます。
逆に、頻繁に変更されるデータをモデル化している場合には、神話のとおりに不変オブジェクトはパフォーマンスの観点で不利になります。
つまり、そのオブジェクトがどのように利用されるのかによって、そのパフォーマンス性能は異なってくるというわけです。
一概にどちらが不利とは言えません。パフォーマンスに関しては不変オブジェクトと可変オブジェクトの間には、複雑なトレードオフがあります。
不変オブジェクトを導入する場合は、そのことを意識しなければならないことは理解しておく必要があります。


ValueObject(不変オブジェクト)の抽象表現

最後にC#で実装したValueObject(不変オブジェクト)の抽象表現をご覧いただきます。
不変オブジェクトを作成するのに非常に便利なクラスですが、リフレクションを利用しているので、
パフォーマンス的にはとても良いとは言えません。これが基で思うようなパフォーマンスがでないような場合は、
Equalsメソッド等を適宜オーバーライドし、リフレクションを用いない軽い実装をすることで対応します。


「派生クラスのフィールドがすべてreadonlyであるか」について契約する不変表明ができればより良いのですが、
現在のCode Contractsの仕様では不可能ですので、派生クラスにreadonlyではないフィールが存在する場合には、
コンストラクタ内でAssertするようにしています。


id:YokoKenさんのご指摘でコードを一部修正しました。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Diagnostics.Contracts;

namespace ClassLibrary1
{
    /// <summary>
    /// ValueObject(不変オブジェクト)の抽象表現
    /// </summary>
    /// <typeparam name="TSelf"></typeparam>
    [Serializable]
    public abstract class ValueObject<TSelf> : IEquatable<TSelf>
        where TSelf : ValueObject<TSelf>
    {
        public ValueObject()
        {
            var fields = GetFields();
            foreach (var field in fields)
            {
                Contract.Assert(field.IsInitOnly,"immutableじゃないっぽいよ");
            }
        }

        public override bool Equals(object obj)
        {
            var other = obj as TSelf;
            if (object.ReferenceEquals(other, null)) return false;
            return Equals(obj as TSelf);
        }

        public override int GetHashCode()
        {
            int hashCode = 11;
            int multiplier = 29;

            var fields = GetFields();

            foreach (var field in fields.Select((x, i) => new { Value = x, Index = i }))
            {
                var value = field.Value.GetValue(this);
                if (value == null) continue;
                hashCode *= multiplier;
                hashCode += (value.GetHashCode() + field.Index);
            }
            return hashCode;
        }

        public virtual bool Equals(TSelf other)
        {
            if (other == null) return false;

            Type t = GetType();
            Type otherType = other.GetType();
            if (t != otherType) return false;

            var fields = t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);

            foreach (var field in fields)
            {
                var x = field.GetValue(other);
                var y = field.GetValue(this);

                if (x == null && y != null) return false;
                if (!x.Equals(y)) return false;
            }
            return true;
        }

        protected IEnumerable<FieldInfo> GetFields()
        {
            Type t = GetType();
            var fields = new List<FieldInfo>();

            while (t != typeof(object))
            {
                fields.AddRange(t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public));
                t = t.BaseType;
            }
            return fields;
        }

        public static bool operator ==(ValueObject<TSelf> x, ValueObject<TSelf> y)
        {
            if (object.ReferenceEquals(x, null) && object.ReferenceEquals(y, null)) return true;
            if (object.ReferenceEquals(x, null)) return false;
            return x.Equals(y);
        }

        public static bool operator !=(ValueObject<TSelf> x, ValueObject<TSelf> y)
        {
            return !(x == y);
        }
    }
}


関数型言語についてもしっくりきたい今日この頃です。
次回は、実際にこのValueObject(不変オブジェクト)の抽象表現を利用した実装サンプルを紹介します。

*1:ある意味釣りともとれるタイトル

C#とF#で、ちょっと草植えときますね型言語 Grassインタプリタを実装してみました。

第47回 CLR/H 勉強会(4/17)に参加しました。今回で6回目くらいですかね。
楽しく有意義な時間を過ごせました。講師の方、参加者のみなさんどうもありがとうございました!
今回はじめて、わたしもライトニングトークに登壇させて頂きました。
もともと人前で話すのは得意ではないのですが、うまくしゃべれなかったことに少しへこんでいます。


第47回 CLR/H 勉強会 zecl LT資料


ちょっと草植えときますね型言語 Grass 公式サイト


というわけで、.NET(C#とF#)で草を生やしてみました。前々から実装したいなと思っていたのが、ようやく実現です。
C#による実装は公式サイトにあるJavaの実装を真似てアレンジしただけなので、掲載は控えます。
以下に、F#による実装を掲載します。とりあえず動くようになった段階では180行近くあったのですが、
リファクタリングをすることでかなりスリムでシンプルなコードになりました。
関数プログラミングの理解をより深めるためにも、リファクタリングをすることはかなり有効だと思いました。



F#でちょっと草植えときますねwww

Console出力で、Shift-JISが文字化けしないように工夫してあります。

open System

type token = Tw | TW | Tv | EoF
type Value = Value of char option * (Value -> Value)

let Write =
  let queue = new System.Collections.Generic.Queue<byte> ()
  (fun _ (c : char) ->
  let (|HEIGHT|_|) x = match x with | x when x >= 0x81uy && x <= 0x9Fuy || x >= 0xE0uy -> Some x | _ -> None
  let (|LOW|_|) x = match x with | x when x >= 0x40uy && x <= 0x7Euy || x >= 0x80uy && x <= 0xFCuy -> Some x | _ -> None
  match c |> byte with
   | HEIGHT x when queue.Count = 0 -> x |> queue.Enqueue 
   | LOW x when queue.Count <> 0 -> [|queue.Dequeue (); x|] |> System.Text.Encoding.GetEncoding(932).GetString |> Console.Write 
   | _ -> Console.Write c) ()

let Char x = Value (Some x, fun y ->
  let True = Value (None, fun x -> Value (None, fun _ -> x))
  let False = Value (None, fun _ -> Value (None, fun y -> y))
  match y with 
   | Value (Some y, _) -> match x,y with | x,y when x = y -> True | _ -> False
   | _ -> raise (new ArgumentException("Char : char以外はだめ")))

let InitStack = [
  Value (None, function
    | Value (Some c, _) as v -> Write c; v
    | _ -> raise (new ArgumentException("primitive Out : char以外はだめ")));  
  Value (None, function
    | Value (Some c, _) -> (int c + 1) % 256 |> char |> Char
    | _ -> raise (new ArgumentException("primitive Succ : char以外はだめ"))); 
  Char 'w';
  Value (None, fun x -> try stdin.ToString() |> char |> Char with eof -> x)]  

let Stack = (fun _ -> let stack = ref InitStack
                      fun x -> match x with 
                                | [] -> !stack 
                                | _  -> stack := x @ !stack; !stack) () 

let ReadFile filename = System.IO.File.ReadAllText(filename,System.Text.Encoding.GetEncoding("SHIFT-JIS")) 
let source = @"c:\Code\Grass\はいはいわろすわろす.txt" |>  ReadFile 

let rec Analyze i = 
  let (|EOF|_|) x = match x with | x when x >= source.Length  -> Some x | _ -> None
  match i with 
   | EOF i -> i, EoF
   | _ -> match source.[i] with
           | 'w'|'w' -> i + 1, Tw
           | 'W'|'W' -> i + 1, TW
           | 'v'|'v' -> i + 1, Tv
           | _ -> Analyze (i + 1)

let rec Read target (index, token as position) i = 
  match token,target with 
    | token,target when token = target -> Read target (Analyze index) (i + 1)
    | _ -> position, i

let rec ReadBody position body = 
  let position, f = Read TW position 0 
  match f with 
   | 0 -> (position, List.rev body) 
   | _ -> let position, a = Read Tw position 0 
          ReadBody position ((f, a) :: body)

let rec App f a stack = 
  match stack with
   | [] -> raise (new ArgumentException("stack"))
   | v::st ->
    match a,f with
     | 1,_ -> let Value = List.nth stack (f - 1) 
              match Value with 
               | Value (c,func) -> func v
     | _,1 -> let arg = List.nth stack (a - 1) 
              let value  =  List.nth stack (f - 1)
              match value with 
               | Value (c,func) -> func arg
     | _   -> st |> App (f - 1) (a - 1)      

let Run = 
  let rec Run (index, token as position) = 
    match token with
      | EoF -> [] |> Stack |> App 1 1 |> ignore
      | Tw ->
        let position, argc = Read Tw position 0 
        let position, body = ReadBody position [] 
        let rec bind n stack arg = 
          let stack = arg :: stack
          match n with
           | 1 -> let rec loop stack body = 
                    match body with
                     | [] -> List.head stack
                     | (f, a) :: [] -> stack |> App f a
                     | (f, a) :: br -> loop ((stack |> App f a) :: stack) br
                  loop stack body
           | _ -> Value (None, bind (n - 1) stack)  
        [Value (None, bind argc (Stack[]))] |> Stack |> ignore
        Run position
      | TW ->
        let position, f = Read TW position 0 
        let position, a = Read Tw position 0
        [Stack [] |> App f a] |> Stack |> ignore
        Run position
      | Tv -> Run (Analyze index)

  let start = 
    let rec loop i = 
      let (index, token) as result = Analyze i 
      match token with
       | Tw | EoF -> result 
       | _ -> loop index
    loop 0
  start |> Run

[<STAThreadAttribute>]
do
  Run; stdin.ReadLine () |> ignore


はいはいわろすわろす.txt

----------------------------------------------------------------------------------------------
wwwwvwvwwWWwvwwWwwvwwwwWWWwwWwwWWWWWWwwwwWw
wvwWWwWwwvwWWWwwwwwWwwwwwwWWwWWWwWWWWWWwW
WWWWWWWwwwWwwWWWWWWWWWWwWwwwwwWWWWWWWW
WWWwwwwwWWWWWWWWWWWWwwwwWWWWWWWWWWWWW
wwwWWWWWWWWWWWWWWwwwWWWWWWWWWWWWWWWwW
WWWWWWWWWWWWWWWWWwwwWwwwwwwwwwwwwwwWWWW
WWWWWWWWWWWWWWWwwwwwwwwWwwWWWWWWWWWWW
WWWWWWWWWWWWWwwwwwwwwwwwwwwwwwwwwwwwwWwwww
wwwwwwwwwwwwwwwww             wwwwwwwwWWwwwwwww
wwwwwwwwwwwwwwwww          は   wwwwwWWWWWWWWWW
WWWWWwWwwwWWWW    わ   い   WWWWWWWwwwWwwWW
WWWWWWWWWWwwww    ろ   は    wWwwwwwwwWWWWWWW
WWWWWWWWWWwwww    す   い   wwwWwwWWWWWWWWW
WWWWWWWWWwwwww     わ       wwwwWwwWWWWWWWW
WWWWWWWWWWWWW    ろ       WWWWWWWWWwwwwww
wwwwwWwwWWWWWWW    す       WWWWWWWWWwwwwww
wwwwwwwWwwwwwwwww             wwwwwwWWWWWWWWW
WWWWWWWWwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
wwwwwwWwwwwwwwwwwwwwwWWwwwwwwwwwWWWwwWWWWwww
wwwwwwwwwwwwWWWWWwwWWWWWWwwwwWWWWWWWwwWWW
WWWWWwwwwWWWWWWWWWwwWWWWWWWWWWwwwwwwwww
wwwwWWWWWWWWWWWWWWWWWWWWWWWWWWWWwwwwww
wwwwwWwwwWWwwwwwwwwwwwwwwwwwwWWWwwWWWWwwwwww
wwwwwwwwwwwwwwwwwwWWWWWwwWWWWWWwwwwwwwWWWW
WWWwwWWWWWWWWwwwwwwWWWWWWWWWwwWWWWWWWW
WWwwwwwwWWWWWWWWWWWwwwwwwwwwwwwwwwwwwwwwww
----------------------------------------------------------------------------------------------


実行結果

はいはいわろすわろす


関数型言語F#の潜在能力を垣間見た気がします。
もっと修行を積めば、さらにシンプルさを追求できるかも!F#流行るといいな〜


おまけ
GUI版もつくりました。シンタックスハイライトきいてますw