Expressions de calcul (F#)

En F#, les expressions de calcul fournissent une syntaxe pratique pour l'écriture de calculs qui peuvent être séquencés et combinés à l'aide de liaisons et de constructions de flux de contrôle. Elles peuvent être utilisées pour fournir une syntaxe pratique pour les monades, qui sont une fonctionnalité de programmation fonctionnelle pouvant être utilisée pour gérer des données, le contrôle et les effets secondaires dans les programmes fonctionnels.

Workflows intégrés

Les expressions de séquence sont un exemple d'expression de calcul, au même titre que les flux de travail asynchrones. Pour plus d'informations sur les expressions de séquence, consultez Séquences. Pour plus d'informations sur les flux de travail asynchrones, consultez Flux de travail asynchrones.

Certaines fonctionnalités sont communes aux expressions de séquence et aux flux de travail asynchrones, et illustrent la syntaxe de base d'une expression de calcul :

builder-name { expression }

La syntaxe précédente spécifie que l'expression donnée est une expression de calcul d'un type spécifié par builder-name. L'expression de calcul peut être un flux de travail intégré, tel que seq ou async, ou peut être quelque chose que vous définissez. builder-name est l'identificateur pour une instance d'un type spécial appelé type de générateur. Le type de générateur est un type de classe qui définit des méthodes spéciales qui régissent la façon dont les fragments de l'expression de calcul sont combinés (autrement dit, le code qui contrôle le mode d'exécution de l'expression). Une autre façon de décrire une classe de générateur consiste à dire qu'elle vous permet de personnaliser l'opération de nombreuses constructions F#, telles que les boucles et les liaisons.

Dans les expressions de calcul, deux formulaires sont disponibles pour quelques constructions de langage courantes. Vous pouvez appeler les constructions de type variant en utilisant un « ! » (bang) sur certains mots clés, tels que let!, do!, etc. Ces formulaires spéciaux forcent certaines fonctions définies dans la classe de générateur à remplacer le comportement intégré ordinaire de ces opérations. Ces formes ressemblent à la forme yield! du mot clé yield utilisé dans les expressions de séquence. Pour plus d'informations, consultez Séquences.

Création d'un type d'expression de calcul

Vous pouvez définir les caractéristiques de vos propres expressions de calcul en créant une classe de générateur et en définissant certaines méthodes spéciales sur la classe. La classe de générateur peut éventuellement définir les méthodes, comme indiqué dans le tableau suivant.

Le tableau suivant décrit les méthodes qui peuvent être utilisées dans une classe de générateur de workflow.

Méthode

La ou les signatures typiques

Description

Bind

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

Appelée pour let! et do! dans les expressions de calcul.

Delay

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

Encapsule une expression de calcul sous forme de fonction.

Return

'T -> M<'T>

Appelée pour return dans les expressions de calcul.

ReturnFrom

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

Appelée pour return! dans les expressions de calcul.

Run

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

M<'T> -> 'T

Exécute une expression de calcul.

Combine

M<'T> * M<'T> -> M<'T> ou

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

Appelée pour le séquencement dans les expressions de calcul.

For

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

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

Appelée pour les expressions for...do dans les expressions de calcul.

TryFinally

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

Appelée pour les expressions try...finally dans les expressions de calcul.

TryWith

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

Appelée pour les expressions try...with dans les expressions de calcul.

Using

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

Appelée pour les liaisons use dans les expressions de calcul.

While

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

Appelée pour les expressions while...do dans les expressions de calcul.

Yield

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

Appelée pour les expressions yield dans les expressions de calcul.

YieldFrom

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

Appelée pour les expressions yield! dans les expressions de calcul.

Zero

unit -> M<'T>

Appelée pour les branches else vides des expressions if...then dans les expressions de calcul.

Beaucoup de méthodes d'une classe de générateur de flux de travail utilisent et retournent toutes une construction M<'T>, qui doit en général être un type défini séparément qui caractérise le genre des calculs combinés, par exemple, Async<'T> pour les flux de travail asynchrones et Seq<'T> pour les flux de travail de séquence. Les signatures de ces méthodes leur permettent d'être combinées et imbriquées les unes dans les autres, afin que l'objet de flux de travail retourné d'une construction puisse être passé à la suivante. Lorsque le compilateur analyse une expression de calcul, il convertit l'expression en une série d'appels de fonction imbriqués à l'aide des méthodes du tableau précédent et du code de l'expression de calcul.

L'expression imbriquée a la forme suivante :

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

Dans le code ci-dessus, les appels à Run et Delay sont omis s'ils ne sont pas définis dans la classe de générateur d'expressions de calcul. Le corps de l'expression de calcul, désigné ici par {| cexpr |}, est traduit en appels concernant les méthodes de la classe de générateur par les traductions décrites dans le tableau suivant. L'expression de calcul {| cexpr |} est définie de manière récursive selon ces traductions où expr est une expression F# et cexpr est une expression de calcul.

Expression

Traduction

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

Dans le tableau précédent, other-expr décrit une expression qui n'est pas autrement répertoriée dans le tableau. Une classe de générateur n'a pas besoin d'implémenter toutes les méthodes et de prendre en charge toutes les traductions répertoriées dans le tableau précédent. Ces constructions qui ne sont pas implémentées ne sont pas disponibles dans les expressions de calcul de ce type. Par exemple, si vous ne souhaitez pas prendre en charge le mot clé use dans vos expressions de calcul, vous pouvez omettre la définition de Use dans votre classe de générateur.

L'exemple de code suivant illustre la création et l'utilisation d'un type d'expression de calcul simple qui génère une sortie de console qui suit la progression de l'exécution du code.

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

Le code suivant implémente la classe de générateur pour l'expression de calcul de trace.

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

La sortie de cet exemple est la suivante.

Starting traced execution.
Binding 2.
Returning result: 3

Voir aussi

Autres ressources

Référence du langage F#

Historique des modifications

Date

Historique

Motif

Décembre 2010

La méthode Run et les tableaux de traductions ont été ajoutés.

Commentaires client.

Avril 2011

Rendement a été ajouté et la signature a été mise à jour pour Pour dans le tableau de méthode de générateur.

Résolution des bogues de contenu.