Expressões de computação (F#)
Expressões de cálculo em F# fornecem uma sintaxe conveniente para a criação de cálculos que podem ser seqüenciados e combinados usando construções de fluxo de controle e ligações. Pode ser usados para fornecer uma sintaxe conveniente para mônadas, um recurso de programação funcional que pode ser usado para gerenciar dados, controle e efeitos colaterais em programas funcionais.
Fluxos de trabalho internos
Expressões de seqüência são um exemplo de uma expressão de cálculo, assim como fluxos de trabalho assíncronos e expressões de consulta. Para obter mais informações, consulte seqüências, Fluxos de trabalho assíncronos, e Expressões de consulta.
Alguns recursos comuns a expressões de seqüência e fluxos de trabalho assíncronos e ilustram a sintaxe básica de uma expressão de cálculo:
builder-name { expression }
A sintaxe anterior Especifica que a expressão especificada é uma expressão de cálculo de um tipo especificado por builder-name. A expressão de cálculo pode ser um fluxo de trabalho interno, como seq ou async, ou pode ser algo que você definir. O builder-name o identificador para uma instância de um tipo especial conhecido como o tipo de construtor. Tipo builder é uma classe que define métodos especiais que controlam a maneira como os fragmentos da expressão de cálculo são combinados, ou seja, código que controla como a expressão é executada. Outra maneira de descrever uma classe de construtor é dizer que ele permite que você personalize a operação de muitas F# construções, como loops e ligações.
Em expressões de cálculo dois formulários estão disponíveis para algumas construções de linguagem comum. Você pode chamar construções variant usando um! (Pronto) sufixo em determinadas palavras-chave, como let!, do!e assim por diante. Esses formulários especiais fazer determinadas funções definidas na classe de construtor para substituir o comportamento interno comum dessas operações. Esses formulários semelhante a yield! forma do yield palavra-chave que é usado em expressões de seqüência. Para obter mais informações, consulte seqüências.
Criando um novo tipo de expressão de cálculo
Você pode definir as características de suas próprias expressões de cálculo, criando uma classe de construtor e definindo determinados métodos especiais na classe. A classe de construtor pode definir os métodos listados na tabela a seguir.
A tabela a seguir descreve os métodos que podem ser usados em uma classe de construtor de fluxo de trabalho.
Método |
Assinatura (s) típico |
Descrição |
Bind |
M<'T> * ('T -> M<'U>) -> M<'U> |
Chamado para let! e do! em expressões de cálculo. |
Delay |
(unit -> M<'T>) -> M<'T> |
Quebra uma expressão de cálculo como uma função. |
Return |
'T -> M<'T> |
Chamado para return em expressões de cálculo. |
ReturnFrom |
M<'T> -> M<'T> |
Chamado para return! em expressões de cálculo. |
Run |
M<'T> -> M<'T>ou M<'T> -> 'T |
Executa uma expressão de cálculo. |
Combine |
M<'T> * M<'T> -> M<'T>ou M<unit> * M<'T> -> M<'T> |
Chamado para seqüenciamento em expressões de cálculo. |
For |
seq<'T> * ('T -> M<'U>) -> M<'U>ou seq<'T> * ('T -> M<'U>) -> seq<M<'U>> |
Chamado para for...do expressões em expressões de cálculo. |
TryFinally |
M<'T> * (unit -> unit) -> M<'T> |
Chamado para try...finally expressões em expressões de cálculo. |
TryWith |
M<'T> * (exn -> M<'T>) -> M<'T> |
Chamado para try...with expressões em expressões de cálculo. |
Using |
'T * ('T -> M<'U>) -> M<'U> when 'U :> IDisposable |
Chamado para use ligações em expressões de cálculo. |
While |
(unit -> bool) * M<'T> -> M<'T> |
Chamado para while...do expressões em expressões de cálculo. |
Yield |
'T -> M<'T> |
Chamado para yield expressões em expressões de cálculo. |
YieldFrom |
M<'T> -> M<'T> |
Chamado para yield! expressões em expressões de cálculo. |
Zero |
unit -> M<'T> |
Chamado para vazio else ramificações de if...then expressões em expressões de cálculo. |
Muitos dos métodos na classe de construtor usar e retornar um M<'T> construção, que geralmente é um tipo definido separadamente que caracteriza o tipo de computações sendo combinado, por exemplo, Async<'T> para fluxos de trabalho assíncronos e Seq<'T> para fluxos de trabalho de seqüência. As assinaturas desses métodos habilitá-los combinados e aninhados com outros, para que o objeto de fluxo de trabalho retornado de uma construção pode ser passado para a próxima. O compilador, quando ele analisa uma expressão de cálculo, converte a expressão em uma série de chamadas de função aninhada usando os métodos na tabela anterior e o código da expressão de cálculo.
A expressão aninhada é da seguinte forma:
builder.Run(builder.Delay(fun () -> {| cexpr |}))
No código acima, as chamadas para Run e Delay são omitidos se não forem definidas na classe de construtor de expressão de cálculo. O corpo da expressão de cálculo aqui indicada como {| cexpr |}, é traduzida em chamadas envolvendo os métodos da classe de construtor por traduções descritas na tabela a seguir. A expressão de cálculo {| cexpr |} é definido recursivamente de acordo com a essas traduções onde expr é uma expressão F# e cexpr é uma expressão de cálculo.
Expressão |
Tradução |
---|---|
{| 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() |
Na tabela anterior, other-expr descreve uma expressão que o contrário não estiver listada na tabela. Uma classe de construtor não precisa implementar todos os métodos e suporte a todas as traduções listadas na tabela anterior. Essas construções não são implementadas não estão disponíveis em expressões de cálculo desse tipo. Por exemplo, se você não deseja oferecer suporte a use palavra-chave em suas expressões de cálculo, você pode omitir a definição de Use na sua classe de construtor.
O exemplo de código a seguir mostra uma expressão de cálculo que encapsula uma computação como uma série de etapas que podem ser avaliadas uma etapa por vez. Um tipo de união de discriminated OkOrException, codifica o estado de erro da expressão conforme avaliado até o momento. Este código demonstra vários padrões típicos que você pode usar em suas expressões de computação, como implementações de clichê de alguns dos métodos de construtor.
// 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
Uma expressão de cálculo tem um tipo subjacente, a expressão retorna. O tipo subjacente pode representar um resultado calculado ou um cálculo de atraso pode ser executado ou podem fornecer uma maneira para iterar por meio de algum tipo de coleção. No exemplo anterior, o tipo subjacente foi Eventually.Para uma expressão de seqüência, o tipo subjacente é IEnumerable. Para uma expressão de consulta, o tipo subjacente é IQueryable. Um fluxo de trabalho asychronous, o tipo subjacente é Async. O Async objeto representa o trabalho a ser realizado para calcular o resultado. Por exemplo, chamar Async.RunSynchronously para executar um cálculo e retornar o resultado.
Operações personalizadas
Você pode definir uma operação em uma expressão de cálculo personalizado e use uma operação personalizada como um operador em uma expressão de cálculo. Por exemplo, você pode incluir um operador de consulta em uma expressão de consulta. Quando você define uma operação personalizada, você deve definir o rendimento e métodos na expressão de cálculo. Para definir uma operação personalizada, colocá-lo em uma classe de construtor para a expressão de cálculo e aplicar o CustomOperationAttribute. Este atributo leva uma seqüência de caracteres como argumento, que é o nome a ser usado em uma operação personalizada. Esse nome entra em escopo no início da chave de abertura da expressão de cálculo. Portanto, você não deve usar identificadores que tenham o mesmo nome de uma operação personalizada neste bloco. Por exemplo, evitar o uso de identificadores, como all ou last em expressões de consulta.
Consulte também
Referência
Fluxos de trabalho assíncronos (F#)