2010-08-05 4 views
6

Disons que nous avons d'une simple citation F #:Génération cotations F # paramétrées

 
type Pet = { Name : string } 
let exprNonGeneric = <@@ System.Func(fun (x : Pet) -> x.Name) @@> 

La citation résultant est comme:

 
val exprNonGeneri : Expr = 
    NewDelegate (System.Func`2[[FSI_0152+Pet, FSI-ASSEMBLY, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], 
      x, PropertyGet (Some (x), System.String Name, [])) 

Maintenant, je veux généraliser, donc je lieu du type « Pet "and property" Name "Je pourrais utiliser un type arbitraire et une méthode/propriété définie dessus. Voici ce que je suis en train de faire:

 
let exprGeneric<'T, 'R> f = <@@ System.Func<'T, 'R>(%f) @@> 
let exprSpecialized = exprGeneric<Pet, string> <@ (fun (x : Pet) -> x.Name) @> 

L'expression résultante est maintenant différente:

 
val exprSpecialized : Expr = 
    NewDelegate (System.Func`2[[FSI_0152+Pet, FSI-ASSEMBLY, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null],[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], 
      delegateArg, 
      Application (Lambda (x, 
            PropertyGet (Some (x), System.String Name, [])), 
          delegateArg)) 

Comme vous pouvez le constater, la différence entre la première et la deuxième expression est que dans le premier cas, la L'expression NewDelegate de niveau supérieur contient PropertyGet, tandis que la deuxième expression enveloppe PropertyGet dans une expression Application/Lambda. Et quand je passe cette expression à un code externe, il ne s'attend pas à une telle structure d'expression et échoue.

J'ai donc besoin d'un moyen de construire une version généralisée de la citation, donc quand elle se spécialise, la citation qui en résulte est une correspondance exacte de < @@ System.Func (fun (x: Pet) -> x.Name) @@ >. Est-ce possible? Ou y-a-t-il seulement un choix pour appliquer manuellement un modèle à un devis généré et le transformer en ce dont j'ai besoin?

MISE À JOUR. Pour contourner ce problème, je l'adaptateur suivant mis en œuvre:

 
let convertExpr (expr : Expr) = 
    match expr with 
    | NewDelegate(t, darg, appl) -> 
     match (darg, appl) with 
     | (delegateArg, appl) -> 
      match appl with 
      | Application(l, ldarg) -> 
       match (l, ldarg) with 
       | (Lambda(x, f), delegateArg) -> 
        Expr.NewDelegate(t, [x], f) 
       | _ -> expr 
      | _ -> expr 
    | _ -> expr 

Il fait le travail - je peux maintenant convertir l'expression du 1er au 2ème forme. Mais je suis intéressé à savoir si cela peut être réalisé de manière simple, sans traverser les arbres d'expression.

Répondre

6

Je ne pense pas que ce soit possible; Dans le second cas, vous connectez l'expression <@ (fun (x : Pet) -> x.Name) @>, représentée à l'aide d'un nœud Lambda, dans le trou de l'autre expression. Le compilateur ne simplifie pas les expressions lors de ce processus de connexion, donc le nœud Lambda ne sera pas supprimé, peu importe ce que vous faites.

Toutefois, votre solution de correspondance de motif peut être grandement simplifié:

let convertExpr = function 
| NewDelegate(t, [darg], Application(Lambda(x,f), Var(arg))) 
    when darg = arg -> Expr.NewDelegate(t, [x], f) 
| expr -> expr 

En fait, votre version plus compliquée est incorrecte. Cela est dû au fait que le delegateArg dans votre modèle le plus interne ne correspond pas à la valeur de l'identificateur delegateArg précédemment lié du motif externe; c'est un nouvel identifiant, fraîchement lié, qui est également appelé delegateArg. En fait, l'identificateur extérieur delegateArg a le type Var list alors que le code interne a le type Expr! Cependant, étant donné la gamme limitée de formes d'expression générées par le compilateur, votre version cassée peut ne pas poser de problème en pratique.

EDIT

En ce qui concerne vos questions de suivi, si je vous comprends bien, il peut ne pas être possible d'obtenir ce que vous voulez. Contrairement à C#, où x => x + 1 pourrait être interprété comme ayant un type de Func<int,int> ou Expression<Func<int,int>>, dans F # fun x -> x + 1 est toujours de type int->int.Si vous souhaitez obtenir une valeur de type Expr<int->int>, vous devez généralement utiliser l'opérateur de devis (<@ @>).

Il existe cependant une alternative qui peut être utile. Vous pouvez utiliser l'attribut [<ReflectedDefinition>] sur les fonctions liées let pour que leurs offres soient également disponibles. Voici un exemple:

open Microsoft.FSharp.Quotations 
open Microsoft.FSharp.Quotations.ExprShape 
open Microsoft.FSharp.Quotations.Patterns 
open Microsoft.FSharp.Quotations.DerivedPatterns 

let rec exprMap (|P|_|) = function 
| P(e) -> e 
| ShapeVar(v) -> Expr.Var v 
| ShapeLambda(v,e) -> Expr.Lambda(v, exprMap (|P|_|) e) 
| ShapeCombination(o,l) -> RebuildShapeCombination(o, l |> List.map (exprMap (|P|_|))) 


let replaceDefn = function 
| Call(None,MethodWithReflectedDefinition(e),args) 
    -> Some(Expr.Applications(e, [args])) 
| _ -> None 


(* plugs all definitions into an expression *) 
let plugDefs e = exprMap replaceDefn e 

[<ReflectedDefinition>] 
let f x = x + 1 

(* inlines f into the quotation since it uses the [<ReflectedDefinition>] attribute *) 
let example = plugDefs <@ fun y z -> (f y) - (f 2) @> 
+0

Merci pour la réponse et une excellente suggestion. Je suis cependant toujours coincé avec le problème connexe: est-il possible de brancher un simple délégué F # (par exemple fun x -> x.Name) dans un devis générique qui est indépendant du type réel, comme la citation # 2 ci-dessus. Ceci est similaire à ce que font les frameworks moqueurs: ils définissent certaines expressions sans connaître d'interfaces concrètes et injectent des types concrets. Je ne semble pas être capable de réaliser cela en F #. –

+0

@Vagif - Je ne suis pas sûr de comprendre votre question. Pourriez-vous nous donner plus de détails sur ce que vous essayez de faire? – kvb

+0

Je dois envoyer interop entre F # et C#. C# attend l'expression LINQ d'un certain type. Je peux l'écrire en F # de façon codée, mais je veux construire un wrapper générique F # pour cela. Ce wrapper devrait être capable de prendre en entrée des lambdas comme "fun x -> x.Name" et de les convertir en citations. Je commence à penser que ce n'est pas possible dans le cas général. –

Questions connexes