ちょっとした小ネタです。F#には非同期ワークフローという非同期処理を同期的に書けるという素敵な機能があります。
MSDNの非同期ワークフローのページからコードを拝借しますが、以下のようなコードが書けます。
open System.Net
open Microsoft.FSharp.Control.WebExtensions
let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
"MSDN", "http://msdn.microsoft.com/"
"Bing", "http://www.bing.com"
]
let fetchAsync(name, url:string) =
async {
try
let uri = new System.Uri(url)
let webClient = new WebClient()
let! html = webClient.AsyncDownloadString(uri)
printfn "Read %d characters for %s" html.Length name
with
| ex -> printfn "%s" (ex.Message);
}
let runAll() =
urlList
|> Seq.map fetchAsync
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
runAll()非同期にURLのデータを読み取って出力するという処理です。非同期の処理をしてるのに同期的な処理のコードと同じように書けるのが特徴です。素敵!!ということで、これを使うとMVVM的にどんなおいしいことがあるのかというと・・・ViewModelからViewを操作するためのMessengerパターンで何か使えそうです。
MVVMをサポートするライブラリの1つPrismではInteractionRequest
open System
open System.Windows
open Microsoft.Practices.Prism.Interactivity.InteractionRequest
open Microsoft.Practices.Prism.Interactivity
let r = InteractionRequest<Confirmation>()
// View側ではRaisedイベントを購読して処理をしているので、ちょっとエミュレート。
r.Raised.Add <| fun e ->
// メッセージボックスを表示してOKが押されたらConfirmedにtrueを設定する。
let result = MessageBox.Show(string e.Context.Content, e.Context.Title, MessageBoxButton.OKCancel)
let confirmation = e.Context :?> Confirmation
confirmation.Confirmed <- (result = MessageBoxResult.OK)
e.Callback.Invoke()
// Raiseした後の続きの処理はコールバックで行う
r.Raise(
Confirmation(Title = "メッセージ", Content = "お試し"),
fun result ->
match result.Confirmed with
| true -> printfn "OKが押されました"
| false -> printfn "Cancelが押されました")
Console.ReadKey() |> ignoreこのコードを実行するとダイアログが表示されOKを押すと「OKが押されました」Cancelを押すと「Cancelが押されました」と表示されます。コメントにもありますがRaiseメソッドを呼び出しているところでコールバックを指定しているので、ちょっと処理が読みにくくなっています。今回はコールバック内の処理は短いですが、ちょっと長くなったり、別メソッドに切り出したりすると、とたんに処理の流れを追うのが大変になります。
Raiseメソッドの非同期ワークフロー対応
ということで以下のようにInteractionRequest
open System
open System.Windows
open Microsoft.Practices.Prism.Interactivity.InteractionRequest
open Microsoft.Practices.Prism.Interactivity
// InteractionRequest<T>のメソッドを拡張
type InteractionRequest<'a when 'a :> Notification> with
/// 非同期ワークフロー対応のRaiseメソッド
member x.RaiseAsync (n : 'a) =
Async.FromContinuations (fun (cont, econt, ccont) ->
// Raiseのコールバックでcont
x.Raise(n, fun r -> cont(r))
)RaiseAsyncメソッドを定義して、FromContinuationsメソッドを使ってAsync
// 非同期ワークフローを使うとコールバックが消えてすっきり
async {
let! result = r.RaiseAsync(Confirmation(Title = "メッセージ", Content = "お試し"))
match result.Confirmed with
| true -> printfn "OKが押されました"
| false -> printfn "Cancelが押されました"
} |> Async.Startコールバックを排除できたので、まるで一連の流の処理のようにコードを書けるようになりました。とても素敵ですね!
非同期ワークフロー対応版のコードの全体を以下に示します。
open System
open System.Windows
open Microsoft.Practices.Prism.Interactivity.InteractionRequest
open Microsoft.Practices.Prism.Interactivity
// InteractionRequest<T>のメソッドを拡張
type InteractionRequest<'a when 'a :> Notification> with
/// 非同期ワークフロー対応のRaiseメソッド
member x.RaiseAsync (n : 'a) =
Async.FromContinuations (fun (cont, econt, ccont) ->
// Raiseのコールバックでcont
x.Raise(n, fun r -> cont(r))
)
let r = InteractionRequest<Confirmation>()
// View側ではRaisedイベントを購読して処理をしているので、ちょっとエミュレート。
r.Raised.Add <| fun e ->
// メッセージボックスを表示してOKが押されたらConfirmedにtrueを設定する。
let result = MessageBox.Show(string e.Context.Content, e.Context.Title, MessageBoxButton.OKCancel)
let confirmation = e.Context :?> Confirmation
confirmation.Confirmed <- (result = MessageBoxResult.OK)
e.Callback.Invoke()
// 非同期ワークフローを使うとコールバックが消えてすっきり
async {
let! result = r.RaiseAsync(Confirmation(Title = "メッセージ", Content = "お試し"))
match result.Confirmed with
| true -> printfn "OKが押されました"
| false -> printfn "Cancelが押されました"
} |> Async.Start
Console.ReadKey() |> ignoreこれは、ViewModelをF#で書くことを真面目に考えてもいいかも?