コンピュテーション式 (F#)
F# のコンピュテーション式には、制御フローの構成要素と束縛を使用してシーケンス化および結合できる計算を記述するための便利な構文が用意されています。これらの計算式を使用すると、モナドの便利な構文が提供されます。モナドとは、関数型プログラムのデータ、制御、および副作用の管理に使用できる関数型プログラミングの機能です。
組み込みのワークフロー
非同期のワークフローとクエリ式としてシーケンス式は、計算式の例です。詳細についてを参照してくださいシーケンス、 非同期ワークフロー、および クエリ式。
一部の機能は、シーケンス式と非同期ワークフローの両方に共通しており、コンピュテーション式の基本的な構文を示します。
builder-name { expression }
この構文は、特定の式が、builder-name によって指定される型のコンピュテーション式であることを指定しています。コンピュテーション式は、seq や async などの組み込みのワークフローである場合や、ユーザーが定義したものである場合があります。builder-name は、ビルダー型と呼ばれる特殊な型のインスタンスの識別子です。ビルダー型は、コンピュテーション式の断片を結合する特別な方法を定義するクラス型、つまり、式の実行方法を制御するコードです。言い換えれば、ビルダー クラスによって、ループや束縛などの F# の多数の構成要素の操作をカスタマイズできます。
コンピュテーション式では、一部の共通言語構成要素のために 2 つの形式を使用できます。バリアントの構成要素を、let! や do! などのように、特定のキーワードに ! (感嘆符) サフィックスを付けて呼び出すことができます。これらの特殊な形式により、ビルダー クラスで定義されている特定の関数によって、これらの操作の通常の組み込み動作が置き換えられます。これらの形式は、シーケンス式で使用される yield キーワードの yield! 形式に似ています。詳細については、「シーケンス」を参照してください。
コンピュテーション式の新しい型の作成
独自のコンピュテーション式の特性を定義するには、ビルダー クラスを作成し、そのクラスで特定の特殊メソッドを定義します。ビルダー クラスでは、次の表に示すメソッドをオプションで定義できます。
ワークフロー ビルダー クラスで使用できるメソッドを次の表に示します。
メソッド |
通常のシグネチャ |
Description |
Bind |
M<'T> * ('T -> M<'U>) -> M<'U> |
コンピュテーション式の let! および do! に対して呼び出されます。 |
Delay |
(unit -> M<'T>) -> M<'T> |
コンピュテーション式を関数としてラップします。 |
Return |
'T -> M<'T> |
コンピュテーション式の return に対して呼び出されます。 |
ReturnFrom |
M<'T> -> M<'T> |
コンピュテーション式の return! に対して呼び出されます。 |
Run |
M<'T> -> M<'T> または M<'T> -> 'T |
コンピュテーション式を実行します。 |
Combine |
M<'T> * M<'T> -> M<'T> または M<unit> * M<'T> -> M<'T> |
コンピュテーション式のシーケンスに対して呼び出されます。 |
For |
seq<'T> * ('T -> M<'U>) -> M<'U> または seq<'T> * ('T -> M<'U>) -> seq<M<'U>> |
コンピュテーション式の for...do 式に対して呼び出されます。 |
TryFinally |
M<'T> * (unit -> unit) -> M<'T> |
コンピュテーション式の try...finally 式に対して呼び出されます。 |
TryWith |
M<'T> * (exn -> M<'T>) -> M<'T> |
コンピュテーション式の try...with 式に対して呼び出されます。 |
Using |
'T * ('T -> M<'U>) -> M<'U> when 'U :> IDisposable |
コンピュテーション式の use 束縛に対して呼び出されます。 |
While |
(unit -> bool) * M<'T> -> M<'T> |
コンピュテーション式の while...do 式に対して呼び出されます。 |
Yield |
'T -> M<'T> |
コンピュテーション式の yield 式に対して呼び出されます。 |
YieldFrom |
M<'T> -> M<'T> |
コンピュテーション式の yield! 式に対して呼び出されます。 |
Zero |
unit -> M<'T> |
コンピュテーション式内の if...then 式の空白の else 分岐に対して呼び出されます。 |
ビルダー クラスでのメソッドの多くを使用して戻る、 M<'T>たとえば結合する計算の種類を特徴付ける、別個に定義された型が、コンス トラクター、 Async<'T>非同期ワークフローとSeq<'T>シーケンス ワークフロー。これらのメソッドのシグネチャは、メソッドを互いに結合したり、入れ子にしたりできるようになっています。これにより、ある構成要素から返されたワークフロー オブジェクトを次の構成要素に渡すことができます。コンパイラは、コンピュテーション式を解析するときに、上の表のメソッドとコンピュテーション式内のコードを使用して、コンピュテーション式を一連の入れ子になった関数呼び出しに変換します。
入れ子になった式は、次のような形式をしています。
builder.Run(builder.Delay(fun () -> {| cexpr |}))
上記のコードで、Run と Delay の呼び出しがコンピュテーション式ビルダー クラスで定義されていない場合、それらの呼び出しは省略されます。コンピュテーション式の本体 (ここでは {| cexpr |} で表される) は、次の表に示す変換によって、ビルダー クラスのメソッドを含む呼び出しに変換されます。コンピュテーション式 {| cexpr |} は、これらの変換に従って再帰的に定義されます。ここで、expr は F# 式で、cexpr はコンピュテーション式です。
式 |
変換 |
---|---|
{| let binding in cexpr |} |
let binding in {| cexpr |} |
{| let! pattern = expr in cexpr |} |
builder.Bind(expr, (fun pattern -> {| cexpr |})) |
{| do! expr in cexpr |} |
builder.Bind(expr1, (fun () -> {| cexpr |})) |
{| yield expr |} |
builder.Yield(expr) |
{| yield! expr |} |
builder.YieldFrom(expr) |
{| return expr |} |
builder.Return(expr) |
{| return! expr |} |
builder.ReturnFrom(expr) |
{| use pattern = expr in cexpr |} |
builder.Using(expr, (fun pattern -> {| cexpr |})) |
{| use! value = expr in cexpr |} |
builder.Bind(expr, (fun value -> builder.Using(value, (fun value -> {| cexpr |})))) |
{| if expr then cexpr0 |} |
if expr then {| cexpr0 |} else binder.Zero() |
{| if expr then cexpr0 else cexpr1 |} |
if expr then {| cexpr0 |} else {| cexpr1 |} |
{| match expr with | pattern_i -> cexpr_i |} |
match expr with | pattern_i -> {| cexpr_i |} |
{| for pattern in expr do cexpr |} |
builder.For(enumeration, (fun pattern -> {| cexpr }|)) |
{| for identifier = expr1 to expr2 do cexpr |} |
builder.For(enumeration, (fun identifier -> {| cexpr }|)) |
{| while expr do cexpr |} |
builder.While(fun () -> expr), builder.Delay({|cexpr |}) |
{| try cexpr with | pattern_i -> expr_i |} |
builder.TryWith(builder.Delay({| cexpr |}), (fun value -> match value with | pattern_i -> expr_i | exn -> reraise exn))) |
{| try cexpr finally expr |} |
builder.TryFinally(builder.Delay( {| cexpr |}), (fun () -> expr)) |
{| cexpr1; cexpr2 |} |
builder.Combine({|cexpr1 |}, {| cexpr2 |}) |
{| other-expr; cexpr |} |
expr; {| cexpr |} |
{| other-expr |} |
expr; builder.Zero() |
この表で、other-expr は表に示されていない式を表します。ビルダー クラスでは、すべてのメソッドを実装して、この表に示されているすべての変換をサポートする必要はありません。実装されていない構成要素は、その型のコンピュテーション式では使用できません。たとえば、コンピュテーション式で use キーワードをサポートしない場合は、ビルダー クラスで Use の定義を省略できます。
一連のステップをすることができます手順を 1 つずつ評価するように、計算をカプセル化する計算式を次のコード例を示します。A 共用体の型が識別されるOkOrException、これまでの評価式のエラー状態をエンコードします。このコードでは、ビルダーのメソッドのいくつかのボイラー プレートの実装などの計算式で使用できるいくつかの一般的なパターンについて説明します。
// Computations that can be run step by step
type Eventually<'T> =
| Done of 'T
| NotYetDone of (unit -> Eventually<'T>)
module Eventually =
// The bind for the computations. Append 'func' to the
// computation.
let rec bind func expr =
match expr with
| Done value -> NotYetDone (fun () -> func value)
| NotYetDone work -> NotYetDone (fun () -> bind func (work()))
// Return the final value wrapped in the Eventually type.
let result value = Done value
type OkOrException<'T> =
| Ok of 'T
| Exception of System.Exception
// The catch for the computations. Stitch try/with throughout
// the computation, and return the overall result as an OkOrException.
let rec catch expr =
match expr with
| Done value -> result (Ok value)
| NotYetDone work ->
NotYetDone (fun () ->
let res = try Ok(work()) with | exn -> Exception exn
match res with
| Ok cont -> catch cont // note, a tailcall
| Exception exn -> result (Exception exn))
// The delay operator.
let delay func = NotYetDone (fun () -> func())
// The stepping action for the computations.
let step expr =
match expr with
| Done _ -> expr
| NotYetDone func -> func ()
// The rest of the operations are boilerplate.
// The tryFinally operator.
// This is boilerplate in terms of "result", "catch", and "bind".
let tryFinally expr compensation =
catch (expr)
|> bind (fun res -> compensation();
match res with
| Ok value -> result value
| Exception exn -> raise exn)
// The tryWith operator.
// This is boilerplate in terms of "result", "catch", and "bind".
let tryWith exn handler =
catch exn
|> bind (function Ok value -> result value | Exception exn -> handler exn)
// The whileLoop operator.
// This is boilerplate in terms of "result" and "bind".
let rec whileLoop pred body =
if pred() then body |> bind (fun _ -> whileLoop pred body)
else result ()
// The sequential composition operator.
// This is boilerplate in terms of "result" and "bind".
let combine expr1 expr2 =
expr1 |> bind (fun () -> expr2)
// The using operator.
let using (resource: #System.IDisposable) func =
tryFinally (func resource) (fun () -> resource.Dispose())
// The forLoop operator.
// This is boilerplate in terms of "catch", "result", and "bind".
let forLoop (collection:seq<_>) func =
let ie = collection.GetEnumerator()
tryFinally (whileLoop (fun () -> ie.MoveNext())
(delay (fun () -> let value = ie.Current in func value)))
(fun () -> ie.Dispose())
// The builder class.
type EventuallyBuilder() =
member x.Bind(comp, func) = Eventually.bind func comp
member x.Return(value) = Eventually.result value
member x.ReturnFrom(value) = value
member x.Combine(expr1, expr2) = Eventually.combine expr1 expr2
member x.Delay(func) = Eventually.delay func
member x.Zero() = Eventually.result ()
member x.TryWith(expr, handler) = Eventually.tryWith expr handler
member x.TryFinally(expr, compensation) = Eventually.tryFinally expr compensation
member x.For(coll:seq<_>, func) = Eventually.forLoop coll func
member x.Using(resource, expr) = Eventually.using resource expr
let eventually = new EventuallyBuilder()
let comp =
eventually { for x in 1 .. 2 do
printfn " x = %d" x
return 3 + 4 }
// Try the remaining lines in F# interactive to see how this
// computation expression works in practice.
let step x = Eventually.step x
// returns "NotYetDone <closure>"
comp |> step
// prints "x = 1"
// returns "NotYetDone <closure>"
comp |> step |> step
// prints "x = 1"
// prints "x = 2"
// returns "NotYetDone <closure>"
comp |> step |> step |> step |> step |> step |> step
// prints "x = 1"
// prints "x = 2"
// returns "Done 7"
comp |> step |> step |> step |> step |> step |> step |> step |> step
計算式は、式を返す基になる型をです。基になる型が、計算の結果または実行することができます、遅延計算可能性または何らかのコレクションを反復処理するための方法があります。前の例では、基になる型をしたEventually。シーケンス式の場合は、基になる型ですIEnumerable<T>。クエリ式は、基になる型にはIQueryable<T>。非同期ワークフローでは、基になる型である非同期。Async結果を計算するために実行する作業を表すオブジェクト。たとえば、呼び出す Async.RunSynchronously するには、計算を実行し、結果を返します。
ユーザー設定の操作
カスタム操作は、計算式の定義し、カスタム操作は、演算子の計算式を使用することができます。たとえば、クエリ式ではクエリの演算子を含めることができます。カスタム操作を定義するときに、利回りを定義する必要があり、計算式内のメソッドの。カスタム操作を定義するには、ビルダー クラスで、計算式を配置し、適用を CustomOperationAttribute。この属性は、カスタムの操作に使用する名前を引数として文字列をとります。この名前のかっこの計算式のスコープに付属しています。そのため、このブロックでは、カスタムの操作と同じ名前を持つ識別子を使用しないでください。たとえば、識別子の使用などを避けるallまたはlastクエリ式。