计算表达式 (F#)
更新:2010 年 12 月
F# 中的计算表达式提供了一种用于编写计算的方便语法,可以通过使用控制流构造和绑定来对这些计算进行排列和组合。 计算表达式可用于为 Monad 提供一种方便语法。Monad 是一种可用于管理函数程序中的数据、控制以及副作用的函数编程功能。
内置工作流
序列表达式与异步工作流一样,都是计算表达式的示例。 有关序列表达式的更多信息,请参见序列。 有关异步工作流的更多信息,请参见异步工作流。
某些功能是序列表达式和异步工作流所共有的,阐释了计算表达式的基本语法:
builder-name { expression }
前面的语法指定给定表达式是由 builder-name 指定的类型的计算表达式。 该计算表达式可以是内置工作流,例如 seq 或 async,也可以是您定义的工作流。 builder-name 是一种称为“生成器类型”的特殊类型的实例的标识符。 生成器类型是一种类类型,可定义用于控制组合计算表达式的各个片段的方式的特殊方法(即控制表达式执行方式的代码)。 另一种描述生成器类的方式是,生成器类使您能够自定义许多 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 |})被翻译为涉及生成器类方法的调用。 根据这些翻译(其中 expr 是 F# 表达式,cexpr 是计算表达式)递归定义计算表达式 {| 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 修复 |