Espressioni di calcolo (F#)

Le espressioni di calcolo in F# forniscono un'utile sintassi per la scrittura di calcoli che possono essere ordinati in sequenza e combinati utilizzando costrutti e associazioni del flusso di controllo. Possono essere utilizzati per fornire un'utile sintassi per i monad, una funzionalità di programmazione funzionale che può essere utilizzata per gestire dati, controllo ed effetti collaterali nei programmi funzionali.

Flussi di lavoro incorporati

Le espressioni di sequenza rappresentano un esempio di espressione di calcolo, in quanto si tratta di flussi di lavoro asincroni. Per ulteriori informazioni sulle espressioni di sequenza, vedere Sequenze. Per ulteriori informazioni sui flussi di lavoro asincroni, vedere Flussi di lavoro asincroni.

Determinate funzionalità sono comuni sia alle espressioni di sequenza che ai flussi di lavoro asincroni e illustrano la sintassi di base per un'espressione di calcolo:

builder-name { expression }

Nella sintassi precedente viene indicato che l'espressione specificata è un'espressione di calcolo di un tipo specificato da builder-name. L'espressione di calcolo può essere un flusso di lavoro incorporato, ad esempio seq o async, oppure un elemento definito dall'utente. builder-name è l'identificatore per un'istanza di un tipo speciale, noto come tipo generatore. Il tipo generatore è un tipo di classe che definisce i metodi speciali che regolano la modalità di combinazione dei frammenti dell'espressione di calcolo, ovvero il codice che controlla la modalità di esecuzione dell'espressione. Un altro modo per descrivere una classe generatore consiste nell'affermare che consente di personalizzare l'operazione di molti costrutti F#, ad esempio cicli e associazioni.

Nelle espressioni di calcolo sono disponibili due forme per alcuni costrutti di linguaggio comuni. È possibile richiamare i costrutti di tipo variant utilizzando il simbolo ! (punto esclamativo) in determinate parole chiave, ad esempio let!, do! e così via. A causa di queste forme speciali, alcune funzioni definite nella classe del generatore sostituiscono il comportamento incorporato comune di queste operazioni. Si tratta di un formato simile a yield! della parola chiave yield utilizzata nelle espressioni sequenza. Per ulteriori informazioni, vedere Sequenza.

Creazione di un nuovo tipo di espressione di calcolo

È possibile definire le caratteristiche delle espressioni di calcolo personalizzate creando una classe generatore e definendo determinati metodi speciali nella classe. La classe generatore può facoltativamente definire i metodi nei modi elencati nella tabella seguente.

Nella tabella seguente sono descritti metodi che possono essere utilizzati in una classe del generatore di flusso di lavoro.

Metodo

Firma tipica

Descrizione

Bind

M<'T> * ('T -> M<'U>) -> M<'U>

Chiamato per let! e do! nelle espressioni di calcolo.

Delay

(unit -> M<'T>) -> M<'T>

Esegue il wrapping di un'espressione di calcolo come funzione.

Return

'T -> M<'T>

Chiamato per return nelle espressioni di calcolo.

ReturnFrom

M<'T> -> M<'T>

Chiamato per return! nelle espressioni di calcolo.

Run

M<'T> -> M<'T> oppure

M<'T> -> 'T

Esegue un'espressione di calcolo.

Combine

M<'T> * M<'T> -> M<'T> oppure

M<unit> * M<'T> -> M<'T>

Chiamato per la sequenziazione nelle espressioni di calcolo.

For

seq<'T> * ('T -> M<'U>) -> M<'U>

seq<'T> * ('T -> M<'U>) -> seq<M<'U>>

Chiamato per le espressioni for...do nelle espressioni di calcolo.

TryFinally

M<'T> * (unit -> unit) -> M<'T>

Chiamato per le espressioni try...finally nelle espressioni di calcolo.

TryWith

M<'T> * (exn -> M<'T>) -> M<'T>

Chiamato per le espressioni try...with nelle espressioni di calcolo.

Using

'T * ('T -> M<'U>) -> M<'U> when 'U :> IDisposable

Chiamato per le associazioni use nelle espressioni di calcolo.

While

(unit -> bool) * M<'T> -> M<'T>

Chiamato per le espressioni while...do nelle espressioni di calcolo.

Yield

M<'T> -> M<'T>

Chiamato per le espressioni yield nelle espressioni di calcolo.

YieldFrom

M<'T> -> M<'T>

Chiamato per le espressioni yield! nelle espressioni di calcolo.

Zero

unit -> M<'T>

Chiamato per i rami else vuoti delle espressioni if...then nelle espressioni di calcolo.

Numerosi metodi in una classe generatore di flusso di lavoro utilizzano e restituiscono un costrutto M<'T>, che corrisponde in genere a un tipo definito separatamente che caratterizza il tipo di calcoli combinati, ad esempio, Async<'T> per flussi di lavoro asincroni e Seq<'T> per flussi di lavoro in sequenza. Le firme consentono a questi metodi di combinarsi e annidarsi tra loro, in modo che l'oggetto del flusso di lavoro restituito da un costrutto possa essere passato al successivo. Nel momento in cui analizza un'espressione di calcolo, il compilatore converte l'espressione in una serie di chiamate di funzioni annidate utilizzando i metodi nella tabella precedente e il codice nell'espressione di calcolo.

L'espressione annidata ha il formato seguente:

builder.Run(builder.Delay(fun () -> {| cexpr |}))

Nel codice precedente le chiamate a Run e Delay vengono omesse se non sono definite nella classe del generatore di espressioni di calcolo. Il corpo dell'espressione di calcolo, qui indicato da {| cexpr |}, viene convertito nelle chiamate che includono i metodi della classe del generatore dalle conversioni descritte nella tabella riportata di seguito. L'oggetto {| cexpr |} dell'espressione di calcolo è definito in modo ricorsivo in base a queste conversioni in cui expr è un'espressione F# e cexpr è un'espressione di calcolo.

Espressione

Conversione

{| 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()

Nella tabella precedente, other-expr descrive un'espressione che non è elencata in altro modo nella tabella. Una classe del generatore non deve necessariamente implementare tutti i metodi e supportare tutte le conversioni presenti nella tabella precedente. I costrutti che non sono implementati non sono disponibili nelle espressioni di calcolo di tale tipo. Se, ad esempio, non si desidera supportare la parola chiave use nelle espressioni di calcolo, è possibile omettere la definizione di Use nella classe del generatore.

Nell'esempio di codice seguente vengono illustrati la creazione e l'utilizzo di un tipo di espressione di calcolo semplice che genera un output di console che consente di tenere traccia dello stato di avanzamento dell'esecuzione del codice.

// 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()

Nel codice seguente viene implementata la classe generatore per l'espressione di calcolo di traccia.

// 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()

Di seguito è riportato l'output di questo esempio.

Starting traced execution.
Binding 2.
Returning result: 3

Vedere anche

Altre risorse

Riferimenti per il linguaggio F#

Cronologia delle modifiche

Data

Cronologia

Motivo

Dicembre 2010

Aggiunta del metodo Run e della tabella delle conversioni.

Commenti e suggerimenti dei clienti.

Aprile 2011

Aggiunta di Yield e aggiornamento della firma per For nella tabella dei metodi del generatore.

Correzione di bug nel contenuto.