F# + WPF + Chart

F#の練習(特にオブジェクト指向らへん)。


さて、試してみよう試してみようと思いつつ絶賛放置プレイ中だった、
Visifire
という、チャートコンポーネントを今回使ってみた。
無性に使ってみたかったのである。なぜなら無償だから。
Silverlight or WPF のコンポーネンツです。WPFど素人の僕でも使えました。


式の評価にかかる時間をグラフ化するというそれだけのSOLUTION。
ダウンロードしてきて、圧縮ファイルを展開するとBinフォルダがこっちを見ているので、その中の『WPFVisifire.Charts.dll』という子をプロジェクトに追加してやります。WPFなので他に『PresentationCore』『PresentationFramework』『WindowsBase』なども追加してやる必要があります。

//#light "off"
namespace StopwatchGUI

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

/// とても計測するクラスです
type CustomStopwatch() = class
    let watch = System.Diagnostics.Stopwatch()
    
    /// 評価したい式をLazyで凍結して渡します
    member self.Time (f:'T Lazy) =
        watch.Reset ()
        watch.Start () ; f.Value |> ignore ; watch.Stop ()
        watch.Elapsed
end

/// とても表示するクラスです
type Viewer = class
    val private window : Window
    val private canvas : Canvas
    val private chart : Visifire.Charts.Chart
    val private dataSeries : Visifire.Charts.DataSeries
    
    new () = Viewer( 500.0, 350.0 )
    new ( width, height ) as self =
        {
            canvas = Canvas( Name="main" )
            window = Window( Title="StopwatchGUI",
                                Width=width+50.0, Height=height+50.0 )
            chart = Visifire.Charts.Chart( Width=width, Height=height )
            dataSeries = Visifire.Charts.DataSeries()
        }
        then
            self.window.Content <- self.canvas
            self.chart.Series.Add self.dataSeries
            self.canvas.Children.Add self.chart |> ignore

    member self.AddItem  caption value =
        let item = Visifire.Charts.DataPoint( YValue=value, AxisXLabel=caption )
        self.dataSeries.DataPoints.Add item
        value

    member self.Show () = self.window.Show ()
end

/// とても大会を主催するクラスです
type ExpressionChampionship( evalCount:int ) = class
    let viewer = Viewer()
    let watch = CustomStopwatch()
    let times = evalCount
    
    new () = ExpressionChampionship( 1 )
    
    member self.Entry (exp:'T Lazy) entryName =
        List.map (fun _ -> (watch.Time exp).TotalMilliseconds) [1..times]
        |> List.average
        |> viewer.AddItem entryName

    member self.MakePublic () = viewer.Show ()
end


戦いの準備は整いました。
試しに以下のように使ってみます。
(評価する式は以前の記事に書いたものをまた持ってきました)*1

open System
open StopwatchGUI

module Test = begin

    let rec sum_a = function
    | [] -> 0
    | x :: xs -> x + (sum_a xs)

    let rec sum_b lst acc =
        match lst with
        | [] -> acc
        | x :: xs -> sum_b xs (x + acc)

    let sum_c = List.fold (+) 0
    let sum_d lst = List.foldBack (+) lst 0


    let main () =
        let ec = ExpressionChampionship( 5 )
        let lst = [1..20000]
        let p = printfn "%A"

        p <| ec.Entry (lazy (sum_a lst)) "normal"
        p <| ec.Entry (lazy (sum_b lst 0)) "tail"
        p <| ec.Entry (lazy (sum_c lst)) "fold"
        p <| ec.Entry (lazy (sum_d lst)) "foldBack"
        ec.MakePublic ()
end

#if COMPILED
[<STAThread()>]
do
    Test.main ()
    let app =  System.Windows.Application()
    app.Run () |> ignore
#endif


すると、アニメーション付きのこのような棒グラフが表示されます。


引数なしの関数を呼び出す場合、
foo ()
のように unit を渡してますよオーラをビシビシ感じさせる書き方か、
foo()
のようにC#等でのメソッド呼び出しを連想させるような書き方にするかで個人的に迷ってます。(謎


と、まあ、こんな感じでした。
おしまい。

*1:F#CTPからVS2010βへの移行に合わせて、一部修正してます