Поделиться через


Выражения вычисления (F#)

Вычислительные выражения языка F# обеспечивают удобный синтаксис для записи вычислений, которые можно упорядочивать и комбинировать с помощью конструкций и привязок потока управления.Они могут служить для обеспечения удобного синтаксиса компонентов 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>

Вызывается для пустых ветвей else выражений if...then в вычислительных выражениях.

Многие из методов в класс построителя используется и вернуть 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 в классе построителя можно опустить.

В следующем примере кода показано выражение вычисления, инкапсулирующий вычислений, ряд шагов, которые могут быть вычисленного один шаг за один раз.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 Объект представляет трудозатрат для вычисления результата.Например, вызов Async.RunSynchronously для выполнения вычислений и возвращают результат.

Пользовательские операции

Можно определить пользовательские операции при вычислении выражения и использовать пользовательские операции в качестве оператора в выражение вычисления.Например оператор запроса можно включить в выражение запроса.При определении пользовательских операций необходимо определить доход и методов при вычислении выражения.Чтобы определить настраиваемые операции, поместить в класс построителя для вычисления выражения, а затем применить CustomOperationAttribute.Этот атрибут принимает строку в качестве аргумента, — это имя, используемое в пользовательской операции.Имя вступает в области в начале открывающая фигурная скобка выражения вычисления.Таким образом не следует использовать идентификаторы, которые имеют одинаковое имя, как пользовательские операции в этом блоке.Например, такие как избегать использования идентификаторов all или last в выражениях запроса.

См. также

Ссылки

Асинхронных рабочих процессов (F#)

последовательности (F#)

выражения запроса (F#)

Другие ресурсы

Справочник по языку F#