F#でWPF --- Elm Architectureを利用したMVVM --- Model側のキャッシュ
下記記事で紹介いたしました構造において、Viewの差分更新は自動で行われるのですが、その他の部分は全て毎回作成することになっています。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング
もし時間のかかる処理が存在する場合は毎回計算するわけにはいきません。そこで簡単なキャッシュ機能を実装します。
作成するアプリ
カウンタとカウンタの値を2倍したテキスト、乱数を生成するボタンからなります。
カウンタと乱数生成ボタンは完全に独立したものとなっています。
実装
とりあえず何も考えずに実装します。
コード内で利用しているDataContextクラスは下記記事を参照してください。
F#でWPF --- Elm Architectureを利用したMVVM - 何でもプログラミング
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="130" Width="300"> <Grid> <!-- カウンタ --> <Button Content="-" Command="{Binding Decrement}" HorizontalAlignment="Left" Margin="29,23,0,0" VerticalAlignment="Top" Width="25"/> <TextBlock Text="{Binding Count}" HorizontalAlignment="Left" Margin="80,25,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/> <Button Content="+" Command="{Binding Increment}" HorizontalAlignment="Left" Margin="115,23,0,0" VerticalAlignment="Top" Width="25"/> <Label Content="x 2 = " HorizontalAlignment="Left" Margin="167,20,0,0" VerticalAlignment="Top"/> <TextBlock Text="{Binding DoubleCount}" HorizontalAlignment="Left" Margin="233,25,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/> <!-- 乱数生成 --> <Button Content="Random" Command="{Binding GenerateRandom}" HorizontalAlignment="Left" Margin="29,61,0,0" VerticalAlignment="Top" Width="75"/> <TextBlock Text="{Binding Random}" HorizontalAlignment="Left" Margin="128,63,0,0" TextWrapping="Wrap" VerticalAlignment="Top"/> </Grid> </Window>
F#
open System open System.Windows type Msg = | Increment | Decrement | GenerateRandom type Model = { Count : int Random : int } member this.DoubleCount = this.Count * 2 let random = Random() let updateModel model msg = match msg with | Increment -> { model with Count = model.Count + 1 } | Decrement -> { model with Count = model.Count - 1 } | GenerateRandom -> { model with Random = random.Next() } [<STAThread>] [<EntryPoint>] let main argv = let window = Application.LoadComponent(Uri("MainWindow.xaml", UriKind.Relative)) :?> Window window.DataContext <- DataContext({ Count = 0; Random = 0 }, updateModel, id) Application().Run(window) |> ignore 0
仮想的に処理を重くする
現状のままでは重い処理が存在しないので、Sleepにて仮想的に処理を重くします。
type Model = { Count : int Random : int } member this.DoubleCount = Threading.Thread.Sleep(1000) this.Count * 2
乱数生成まで遅くなる
毎回ViewModelを作成しているため(厳密にはDataContextクラス内にて差分を取る際に全プロパティのgetterが呼ばれるため)、乱数生成の処理まで重くなってしまっています。
Cache
mutable Option、input、input -> outputを受け取り、inputがキャッシュと同じならキャッシュを返し、それ以外は新たに処理をして返す&キャッシュに収納する関数を作成します。
module Cache = let get (cache : byref<_>) x f = match cache with | Some (_x, _y) when _x = x -> _y | _ -> let y = f x cache <- Some (x, y) y
Cache組み込み
type Model = { Count : int Random : int mutable Cache_DoubleCount : (int * int) option } member this.DoubleCount = (fun x -> Threading.Thread.Sleep(1000) x * 2) |> Cache.get &this.Cache_DoubleCount this.Count
乱数生成への影響がなくなる
Cache組み込み後は、乱数生成を行ってもカウンタの値が変更されていないので、DoubleCountの値は前回の結果が利用されます。
updateModelでの更新との違い
Cacheを利用しなくても、updateModelの段階で処理して代入しておけば同じ効果が得られます。
ただ入力の数が増えた場合に関連する全てのメッセージ内で代入処理をしなければならないため、Cacheを利用したほうが楽に管理できます。