计算表达式 (F#)

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

'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 的定义。

下面的代码示例显示了一系列步骤,可以在每次计算一步封装计算计算表达式。A discriminated 联合类型, 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。此属性接受一个字符串作为参数,这是要在自定义操作中使用的名称。此名称范围的左大括号,计算表达式的开头开始。因此,不应使用具有相同的名称自定义操作在此块中的标识符。例如,如避免使用标识符alllast在查询表达式中。

请参见

参考

异步工作流 (F#)

序列 (F#)

查询表达式 (F#)

其他资源

F# 语言参考