非同期ワークフロー
次にもうひとつのビルトインのコンピュテーションエクスプレッションである非同期ワークフローについて説明します。
F#では、非同期ワークフローというコンピュテーションエクスプレッションを用いる非同期な計算をサポートしています。非同期というのは、他の作業の実行を妨げないということです。計算が、現在のスレッドで実行が継続されるように、非同期で(バックグラウンドスレッドで)実行されます。

非同期ワークフローの構文は以下のようになります。エクスプレッション部分には、同期あるいは非同期の計算がF#コードで記述されます。
async { エクスプレッション }
非同期処理を実行する方法はいくつかありますが、一般的なのは、Asyncクラスオブジェクト(非同期ワークフロー)を作成し、トリガー関数のような関数を利用して実行する方法です。
以下のリスト5は、リストurlsの各要素のWEBサイトのstringの長さを非同期ワークフローを用いて計算して表示させるものです。
open System.Net
open Microsoft.FSharp.Control.WebExtensions
let urls = ["http://www.yahoo.co.jp/"; " http://codezine.jp/"]
let testWebLength(url: string) =
async {
let uri = new System.Uri(url)
let wc = new System.Net.WebClient()
let! testsite = wc.AsyncDownloadString(uri) //※1 非同期処理
printfn "%d characters" testsite.Length
}
//※2 以下トリガーとなる関数
let testRun () =
urls
|> Seq.map testWebLength
|> Async.Parallel //※3 複数処理をまとめて実行
|> Async.RunSynchronously //※4 非同期ワークフロー実行
|> ignore //戻り値がunitなので
testRun();;
▼
26149 characters 92954 characters val urls : string list = ["http://www.yahoo.co.jp/"; " http://codezine.jp/"] val testWebLength : string -> Async<unit> val testRun : unit -> unit
testWebLengthは、与えられた文字列(url)が示すWEBサイトのページの文字列を非同期にダウンロードし、その長さを表示します。
非同期ワークフローのエクスプレッションには、同期される式もあります。メソッドを非同期に呼び出す場合は、letバインディングではなく、let!を使用します(※1)。let!を使用すると、計算の実行時に、他の計算またはスレッドに対して計算を続行できます。let!バインディングの右側の値(Async<’T>型)が戻されると、他の非同期ワークフローの計算が再開します。
※2において非同期ワークフローは作成され実行されるのを待つ状態にあります。
その後、Asyncクラスのトリガー関数となるもので非同期ワークフローを開始させる必要があります。このとき、リスト5では、URLのリストを引数として渡し各要素に対する、非同期ワークフロー計算を平行して実行できるようにAsync.Parallel関数(※3)でまとめ、Async.RunSynchronously関数(※4)で戻り値のある非同期処理を実行するtestRunメソッドを作成してます。そして、最後にこのtestRunメソッドを実行して非同期ワークフローが実行されます。
次により汎用的なコンピュテーションエクスプレッションの概念について説明します。
コンピュテーションエクスプレッション
コンピュテーションエクスプレッションは構造体を独自のルールにて構文解析し生成・操作し、シーケンスエクスプレッションや非同期ワークフローはこの生成・操作する構造体をシーケンスや非同期処理に特化したものなのです。
コンピュテーションエクスプレッションは、独自の構文解析ルールに従って計算したいコード(エクスプレッション)部分(図※1)と、その独自ルールを定義するライブラリコンポーネントから成り立ちます。このライブラリコンポーネントをビルダー(図※2)と呼び、そのためのクラスをビルダークラスと言います。

以下はこの汎用的なコンピュテーションエクスプレッションの例です。
- バインド時に“Binding xxx(値)”と処理内容を表示する
- returnが、”Returning xxx(値)“と処理内容を表示する
以上のようなルールで構文解析するコンピュテーションエクスプレッションを作成するとします。
まず、構文解析ルールとなる、ビルダークラスをモジュール、testModuleとして定義します。
module testModule
//※1 ビルダーのメソッドを実装する関数
let dobind x testfunc =
printfn "Binding %A." x
testfunc x
let doreturn x =
printfn "Returning %A" x
fun () -> x //エクスプレッションが値を戻す事を可能にする。
//※2 コンピュテーションエクスプレッション用ビルダークラス
type TestBuilder() =
member this.Bind(x, testfunc) =
dobind x testfunc
member this.Return(x) = doreturn x
//※3 グローバルインスタンス作成
let testce = new TestBuilder()
ビルダークラス内に直接ルールとなる関数(例えばprintfn "Binding %A." x)を記述することが可能ですが、上記の例では、それらの関数をメソッドを実装する関数としてをあらかじめ定義しています(※1)。
ビルダークラス定義の構文は以下のようになります。
type クラス名 =
member 識別子.メソッド(引数) =
ビルダーオブジェクトとして定義される関数
:
:
メソッドの部分にはコンピュテーションエクスプレッションの計算(エクスプレッション部分)内で使用できるメソッドが入りメンバーとして定義されます。ビルダークラスにて定義できるメソッドの種類とその呼び出し方の詳細はマイクロソフトなどの資料で確認してください。
このように定義されたビルダークラスは通常、グローバルインスタンスをひとつ持ちます(※3)。以上の例では、testceがそれになります。これがコンピュテーションエクスプレッションの構文においてビルダーオブジェクトネームになります。
次に上記のルールで構文解析させるエクスプレッション(計算)の部分です。
open testModule
//testceオブジェクトを生成
let testCE = testce {
let a = 1
let! b = 2 //※4 エクスプレッション内のlet!はビルダークラスのBindメソッドを呼ぶ
let! c = 3 //※5
let testSum = a + b + c
return testSum //※6 エクスプレッション内のreturnはビルダークラスのReturnメソッドを呼ぶ
}
// コードを実行
let testresult = testCE()
▼
Binding 2. Binding 3. Returning 6
コンピュテーションエクスプレッションは以下のような基本構文で表すことができます。エクスプレッションには、独自の構文解析で計算したい式を記述します。
ビルダーオブジェクトネーム {エクスプレッション}
リスト7のエクスプレッション部分{}に記述されている計算は、変数に3つの値をバインドさせ、うち二つはバインドを独自の解析ルールを使用して行い、3つの値の合計を返すという計算です。※4、※5、※6の部分では、ビルダークラスに定義されたメソッドを呼んでいます。
let!は通常のletバインドではなく、ビルダークラスのBindメソッドを呼びます。したがって、リスト7では、※4の部分は”Binding 2”と表示しながら、バインドを行います。同様に、return(※6)はビルダークラスのReturnを呼びます。Returnはdoreturnを呼び、Returning xxx(testSumの値)を表示します。
まとめ
コンピュテーションエクスプレッションはF#のもっとも優れた機能の一つでもあります。単独の機能として考えるのではなく、シーケンスエクスプレッションと非同期ワークフローを組み合わせるなど、用に応じて適切な種類のコンピュテーションエクスプレッションを使いこなしてください。
以上でC#プログラマのためのF#入門は終わります。本連載では基本機能を解説しましたが、既存プログラムと併用しながらF#を応用することで、より効率的なプログラム作成を目指してください。ありがとうございました。
