Generate parameterized quotes F #

Say we have a simple quote from F #:

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

The resulting quote looks like this:

  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, []))

Now I want to generalize it, so instead of the type β€œPet” and the property β€œName”, I could use an arbitrary type and the method / property defined on it. Here is what I am trying to do:

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

The resulting expression is now different:

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

As you can see, the difference between the first and second expression is that in the first case, the top-level NewDelegate expression contains a PropertyGet, and the second expression wraps the PropertyGet in the Application / Lambda expression. And when I pass this expression to the external code, it does not expect such an expression structure and fails.

So, I need to somehow build a generalized version of the quote, so when it becomes specialized, the resulting quote is an exact match <@@ System.Func (fun (x: Pet) β†’ x.Name) @@>. Is it possible? Or is it just a choice to manually apply pattern matching to the generated quote and convert it to what I need?

UPDATE As a workaround, I implemented the following adapter:

  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

Performs this task - now I can convert the expression from 1st to 2nd form. But I am interested to know if this can be achieved in a simple way, without crossing the expression trees.

+6
f # quotations
source share
1 answer

I do not think it will be possible; in the second case, you insert the expression <@ (fun (x : Pet) -> x.Name) @> , which is represented using the Lambda node, into the hole in another expression. The compiler does not simplify the expressions during this connection process, so the Lambda node will not be deleted no matter what you do.

However, the way to bypass the template can be greatly simplified:

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

Actually your more complicated version is wrong. This is because the delegateArg in your internal template does not match the value of the previously bound delegateArg identifier from the external template; This is a new, freshly linked identifier, also called delegateArg . In fact, the external delegateArg identifier is of type Var list , and the internal is of type Expr ! However, given the limited range of expression forms generated by the compiler, your broken version may be problematic in practice.

EDIT

As for your follow-up questions, if I understand you correctly, you may not be able to achieve what you want. Unlike C #, where x => x + 1 can be interpreted as having the type either Func<int,int> , or Expression<Func<int,int>> , in F # fun x -> x + 1 always of type int->int . If you want to get a value of type Expr<int->int> , you usually need to use the quote operator (<@ @>) .

However, there is one alternative that may be useful. You can use the [<ReflectedDefinition>] attribute so that related functions can also make suggestions. Here is an example:

 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 fx = x + 1 (* inlines f into the quotation since it uses the [<ReflectedDefinition>] attribute *) let example = plugDefs <@ fun yz -> (fy) - (f 2) @> 
+6
source share

All Articles