計算運算式 (F#)
更新:2010 年 12 月
F# 中的計算運算式提供便利的語法,撰寫可以使用控制流程建構和繫結進行排序和合併的計算。 它們可以用來提供 Monad 的便利語法,這是一種函式程式設計功能,可用來管理函式程式中的資料、控制項和副作用。
內建工作流程
序列運算式和非同步工作流程都是計算運算式的範例。 如需序列運算式的詳細資訊,請參閱序列。 如需非同步工作流程的詳細資訊,請參閱非同步工作流程。
某些功能是序列運算式和非同步工作流程通用的,並說明計算運算式的基本語法:
builder-name { expression }
前一個語法指定所指定的運算式是計算運算式,而計算運算式的型別是透過 builder-name 所指定。 計算運算式可以是內建工作流程 (例如 seq 或 async),也可以是您所定義的任何項目。 builder-name 為特殊型別 (稱為「產生器型別」(Builder Type)) 之執行個體的識別項。 產生器型別為定義特殊方法的類別型別,而這些方法可管控計算運算式片段的合併方式,即控制運算式執行方式的程式碼。 另一種描述產生器類別的方法說明它可讓您自訂多個 F# 建構 (例如迴圈和繫結) 的作業。
在計算運算式中,有兩個形式可用於某些通用語言建構。 您可以叫用變數建構,方式是使用 ! (驚嘆號) 後置字元,例如 let!、do! 等等,即可叫用各種建構。 這些特殊形式會讓產生器類別中定義的某些函式取代這些作業的一般內建行為。 這些形式類似序列運算式中使用之 yield! 形式的 yield 關鍵字。 如需詳細資訊,請參閱序列。
建立計算運算式的新型別
建立產生器類別以及在類別上定義某個特殊方法,就可以定義專屬計算運算式的特性。 產生器類別可以選擇性地定義下表所列的方法。
下表描述可在工作流程產生器類別中使用的方法。
方法 |
一般簽章 |
說明 |
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 |
M<'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 的定義。
下列程式碼範例說明如何建立和使用簡單計算運算式型別,而這種型別產生一些可追蹤程式碼執行進度的主控台輸出。
// Program.fs
open Module1
// Create the computation expression object.
let trace1 = trace {
// A normal let expression (does not call Bind).
let x = 1
// A let expression that uses the Bind method.
let! y = 2
let sum = x + y
// return executes the Return method.
return sum
}
// Execute the code. Start with the Delay method.
let result = trace1()
下列程式碼會實作追蹤計算運算式的產生器類別。
// Module1.fs
module Module1 =
// Functions that implement the builder methods.
let bind value1 function1 =
printfn "Binding %A." value1
function1 value1
let result value1 =
printfn "Returning result: %A" value1
fun () -> value1
let delay function1 =
fun () -> function1()
// The builder class for the "trace" workflow.
type TraceBuilder() =
member x.Bind(value1, function1) =
bind value1 function1
member x.Return(value1) = result value1
member x.Delay(function1) =
printfn "Starting traced execution."
delay function1
let trace = new TraceBuilder()
這個範例的輸出如下。
Starting traced execution.
Binding 2.
Returning result: 3
請參閱
其他資源
變更記錄
日期 |
記錄 |
原因 |
---|---|---|
2010 年 12 月 |
已加入 Run 方法和轉譯表。 |
客戶回函。 |
2011 年 4 月 |
已加入 Yield 且已更新產生器方法表中 For 的簽章。 |
內容 Bug 修正。 |