コンピュテーション式の概要
F#のコンピュテーション式とは、「制御フローを構成する要素」と「バインディング」を使用して、順序付けされた計算を組み合わせることができる便利な式構文のことを言います。計算式の種類によっては、モナド・モノイド・モナド変換子・適用可能な関数を表現する方法として考えることができます。ただし、他の関数型言語の機能(例えば、Haskellの do記法 など)とは異なり、これらは単一の抽象化に結び付けられていません。また、状況依存の構文を実現するために、マクロやその他の形式のメタプログラミングにも依存しません。
注) コンピュテーション式は「モナド」と密接な関わりがありますが、そこについては本トピックでは掘り下げません。また、詳細な説明もしないつもりです。あくまでもごく簡単な入門内容程度の紹介に止めます。コンピュテーション式について詳しく知りたい方は、bleis先生の「詳説コンピュテーション式」が非常にわかりやすく情報をまとめてくださっていますので、そちらをご参照ください。
「計算処理」はさまざまな形式をとります。計算処理の最も一般的な形式は「シングルスレッド実行」です。これに対しては理解と修正が容易に行えます。ただし、計算処理のすべてがシングルスレッド実行のように単純なわけではありません。例をいくつかあげます。
非決定論的計算 |
非同期計算 |
エフェクトフル計算 |
生成計算 |
より一般的には、アプリケーションの特定の部分で実行しなければならない状況依存の計算処理があります。しかし、処理の抽象化をせずに状況依存のコードを書くということは、その状況とは違った外部に計算処理を「漏らす」ことが容易であるということを意味します。つまり、状況依存のコードが局所化せずに辺り一面に散らばってしまうということです(また、その可能性が非常に高くなります)。
この手の処理の抽象化は自分自身で定義し、記述することが難しい場合が多いため、F#には コンピュテーション式 と呼ばれる方法があらかじめ用意されました。「コンピューテーション式」ではなく、「コンピュテーション式」ですので間違えて覚えないようにしましょう。
コンピュテーション式は、状況依存の計算をエンコードするための統一された構文と抽象化されたモデルを提供します。
すべてのコンピュテーション式はビルダー型(= builder type)によって形成されています。ビルダー型はコンピュテーション式に使用できる操作を定義します。自作のコンピュテーション式を作成することもできますが、今回のトピックでは紹介しません。
構文の概要
すべてのコンピュテーション式は、以下のような形式の宣言をとります。
builder-expr { cexper }
この [ builder-expr ] には、コンピュテーション式を定義するビルダー型の型名を指定します。そして [ cexper ] に式の本体を記述します。例えば asyncコンピュテーション式 は次のようになります。
// 以下の関数はサンプルのため, 動作はしません.
let fetchAndDownload url =
async {
let! data = downloadData url
let processedData = processData data
return processedData
}
上記のサンプルで気づいた方がいるかもしれませんが、[ let! ] のようにコンピュテーション式内でのみ使用可能な特別な追加構文があります。以下がコンピュテーション式内でのみ利用できるキーワードの一覧です。
expr { let! ... }
expr { do! ... }
expr { yield ... }
expr { yield! ... }
expr { return ... }
expr { return! ... }
expr { match! ... }
これらの各キーワードとその他の標準的なF#キーワードは、バッキングビルダー型で定義されている場合にのみ、コンピュテーション式で使用可能です。ビルダー型は、多くのF#構成要素(たとえば、ループや束縛)の動作をカスタマイズするための機能だと言えます。
組み込みコンピュテーション式
F#のコアライブラリには、シーケンス式(seq)・非同期処理(async)・クエリ式(query)という3つの組み込みコンピュテーション式が定義されています。次節以降ではこれら3つの組み込みコンピュテーション式について紹介していきます。
また、コンピュテーション式は自作することも可能です。これについても次節以降で紹介したいと思います。