Partager via


Quotations de code

Cet article décrit les quotations de code, une fonctionnalité de langage qui vous permet de générer et d’utiliser des expressions de code F# programmatiquement. Cette fonctionnalité vous permet de générer une arborescence de syntaxe abstraite qui représente du code F#. L’arborescence de syntaxe abstraite peut ensuite être parcourue et traitée en fonction des besoins de votre application. Par exemple, vous pouvez utiliser l’arborescence pour générer du code F# ou générer du code dans un autre langage.

Expressions quotées

Une expression quotée est une expression F# délimitée dans votre code pour indiquer qu’elle ne doit pas être compilée dans le cadre de votre programme, mais dans un objet qui représente une expression F#. Vous pouvez marquer une expression quotée de deux façons : avec des informations de type ou sans informations de type. Si vous voulez ajouter des informations de type, utilisez les symboles <@ et @> pour délimiter l’expression quotée. Si vous n’avez pas besoin d’informations de type, utilisez les symboles <@@ et @@>. Le code suivant montre des quotations typées et non typées.

open Microsoft.FSharp.Quotations
// A typed code quotation.
let expr : Expr<int> = <@ 1 + 1 @>
// An untyped code quotation.
let expr2 : Expr = <@@ 1 + 1 @@>

Le parcours d’une grande arborescence d’expression est plus rapide si vous n’ajoutez pas d’informations de type. Le type résultant d’une expression quotée avec les symboles typés est Expr<'T>, où le paramètre de type a le type de l’expression, lui-même déterminé par l’algorithme d’inférence de type du compilateur F#. Quand vous utilisez des quotations de code sans informations de type, le type de l’expression quotée est le type non générique Expr. Vous pouvez appeler la propriété Raw sur la classe typée Expr pour obtenir l’objet non typé Expr.

Il y a différentes méthodes statiques pour générer des objets d’expression F# programmatiquement dans la classe Expr sans utiliser d’expressions quotées.

Une quotation de code doit inclure une expression complète. Pour une liaison let, par exemple, vous avez besoin à la fois de la définition du nom lié et d’une autre expression qui utilise la liaison. Dans la syntaxe détaillée, il s’agit d’une expression qui suit le mot clé in. Au niveau du module, il s’agit simplement de l’expression suivante dans le module, mais dans une quotation, elle est explicitement demandée.

Par conséquent, l’expression suivante n’est pas valide.

// Not valid:
// <@ let f x = x + 1 @>

Alors que les expressions suivantes sont valides.

// Valid:
<@ let f x = x + 10 in f 20 @>
// Valid:
<@
    let f x = x + 10
    f 20
@>

Pour évaluer les quotations F#, vous devez utiliser l’Évaluateur de quotation F#. Il prend en charge l’évaluation et l’exécution d’objets d’expression F#.

Les quotations F# conservent également les informations de contrainte de type. Prenons l’exemple suivant :

open FSharp.Linq.RuntimeHelpers

let eval q = LeafExpressionConverter.EvaluateQuotation q

let inline negate x = -x
// val inline negate: x: ^a ->  ^a when  ^a : (static member ( ~- ) :  ^a ->  ^a)

<@ negate 1.0 @>  |> eval

La contrainte générée par la fonction inline est conservée dans la quotation de code. La forme faisant l’objet d’une quotation de la fonction negate peut désormais être évaluée.

Type Expr

Une instance du type Expr représente une expression F#. Les types génériques et non génériques Expr sont documentés dans la documentation de la bibliothèque F#. Pour plus d’informations, consultez Espace de noms FSharp.Quotations et Classe Quotations.Expr.

Opérateurs d’épissage

L’épissage vous permet de combiner des quotations de code littérales avec des expressions que vous avez créées programmatiquement ou à partir d’une autre quotation de code. Les opérateurs % et %% vous permettent d’ajouter un objet d’expression F# dans une quotation de code. Vous utilisez l’opérateur % pour insérer un objet d’expression typée dans une quotation typée, vous utilisez l’opérateur %% pour insérer un objet d’expression non typée dans une quotation non typée. Les deux opérateurs sont des opérateurs de préfixe unaire. Ainsi, si expr est une expression non typée de type Expr, le code suivant est valide.

<@@ 1 + %%expr @@>

Et si expr est une expression typée de type Expr<int>, le code suivant est valide.

<@ 1 + %expr @>

Exemple 1

Description

L’exemple suivant illustre l’utilisation des quotations de code pour placer du code F# dans un objet d’expression, puis imprimer le code F# qui représente l’expression. Une fonction println est définie et contient une fonction print récursive qui affiche un objet d’expression F# (de type Expr) dans un format convivial. Il y a plusieurs modèles actifs dans les modules FSharp.Quotations.Patterns et FSharp.Quotations.DerivedPatterns qui peuvent être utilisés pour analyser des objets d’expression. Cet exemple n’illustre pas tous les modèles possibles qui peuvent apparaître dans une expression F#. Tout modèle non reconnu déclenche une correspondance avec le modèle de caractère générique (_) et est rendu avec la méthode ToString, qui, sur le type Expr, vous indique le modèle actif à ajouter à votre expression de correspondance.

Code

module Print
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns

let println expr =
    let rec print expr =
        match expr with
        | Application(expr1, expr2) ->
            // Function application.
            print expr1
            printf " "
            print expr2
        | SpecificCall <@@ (+) @@> (_, _, exprList) ->
            // Matches a call to (+). Must appear before Call pattern.
            print exprList.Head
            printf " + "
            print exprList.Tail.Head
        | Call(exprOpt, methodInfo, exprList) ->
            // Method or module function call.
            match exprOpt with
            | Some expr -> print expr
            | None -> printf "%s" methodInfo.DeclaringType.Name
            printf ".%s(" methodInfo.Name
            if (exprList.IsEmpty) then printf ")" else
            print exprList.Head
            for expr in exprList.Tail do
                printf ","
                print expr
            printf ")"
        | Int32(n) ->
            printf "%d" n
        | Lambda(param, body) ->
            // Lambda expression.
            printf "fun (%s:%s) -> " param.Name (param.Type.ToString())
            print body
        | Let(var, expr1, expr2) ->
            // Let binding.
            if (var.IsMutable) then
                printf "let mutable %s = " var.Name
            else
                printf "let %s = " var.Name
            print expr1
            printf " in "
            print expr2
        | PropertyGet(_, propOrValInfo, _) ->
            printf "%s" propOrValInfo.Name
        | String(str) ->
            printf "%s" str
        | Value(value, typ) ->
            printf "%s" (value.ToString())
        | Var(var) ->
            printf "%s" var.Name
        | _ -> printf "%s" (expr.ToString())
    print expr
    printfn ""


let a = 2

// exprLambda has type "(int -> int)".
let exprLambda = <@ fun x -> x + 1 @>
// exprCall has type unit.
let exprCall = <@ a + 1 @>

println exprLambda
println exprCall
println <@@ let f x = x + 10 in f 10 @@>

Output

fun (x:System.Int32) -> x + 1
a + 1
let f = fun (x:System.Int32) -> x + 10 in f 10

Exemple 2

Description

Vous pouvez également utiliser les trois modèles actifs du module ExprShape pour parcourir des arborescences d’expression avec moins de modèles actifs. Ces modèles actifs peuvent être utiles si vous voulez parcourir une arborescence et que vous n’avez pas besoin de toutes les informations de la plupart des nœuds. Quand vous utilisez ces modèles, les expressions F# correspondent à un des trois modèles suivants : ShapeVar si l’expression est une variable, ShapeLambda si l’expression est une expression lambda ou ShapeCombination si l’expression est autre chose. Si vous parcourez une arborescence d’expression avec les modèles actifs comme dans l’exemple de code précédent, vous devez utiliser beaucoup plus de modèles pour gérer tous les types d’expression F# possibles, et votre code devient plus complexe. Pour plus d’informations, consultez Modèle actif ExprShape.ShapeVar|ShapeLambda|ShapeCombination.

L’exemple de code suivant peut être utilisé comme base pour des parcours plus complexes. Dans ce code, une arborescence d’expression est créée pour une expression qui implique un appel de fonction, add. Le modèle actif SpecificCall est utilisé pour détecter les appels à add dans l’arborescence d’expression. Ce modèle actif attribue les arguments de l’appel à la valeur exprList. Dans ce cas, il n’y en a que deux, donc ils sont extraits et la fonction est appelée de manière récursive sur les arguments. Les résultats sont insérés dans une quotation de code qui représente un appel à mul avec l’opérateur d’épissage (%%). La fonction println de l’exemple précédent est utilisée pour afficher les résultats.

Le code dans les autres branches de modèle actif regénère simplement la même arborescence d’expression, le seul changement apporté à l’expression résultante est donc le passage de add à mul.

Code

module Module1
open Print
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.DerivedPatterns
open Microsoft.FSharp.Quotations.ExprShape

let add x y = x + y
let mul x y = x * y

let rec substituteExpr expression =
    match expression with
    | SpecificCall <@@ add @@> (_, _, exprList) ->
        let lhs = substituteExpr exprList.Head
        let rhs = substituteExpr exprList.Tail.Head
        <@@ mul %%lhs %%rhs @@>
    | ShapeVar var -> Expr.Var var
    | ShapeLambda (var, expr) -> Expr.Lambda (var, substituteExpr expr)
    | ShapeCombination(shapeComboObject, exprList) ->
        RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList)

let expr1 = <@@ 1 + (add 2 (add 3 4)) @@>
println expr1
let expr2 = substituteExpr expr1
println expr2

Sortie

1 + Module1.add(2,Module1.add(3,4))
1 + Module1.mul(2,Module1.mul(3,4))

Voir aussi