生成参数化F#报价

Vag*_*lov 6 f# quotations

假设我们有一个简单的F#报价:

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

结果引用如下:

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

现在我想概括它,所以我不是键入"Pet"和属性"Name",而是可以使用在其上定义的任意类型和方法/属性.这是我想要做的:

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

结果表达式现在不同了:

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

如您所见,第一个和第二个表达式之间的区别在于,在第一种情况下,顶级NewDelegate表达式包含PropertyGet,而第二个表达式在Property/Lambda表达式中包装PropertyGet.当我将这个表达式传递给外部代码时,它不会期望这样的表达式结构并且失败.

所以我需要一些方法来构建一个通用版本的引用,所以当它被专门化时,结果引用与<@@ System.Func(fun(x:Pet) - > x.Name)@@>完全匹配.这可能吗?或者只有选择手动将模式匹配应用于生成的报价并将其转换为我需要的?

更新.作为一种解决方法,我实现了以下适配器:

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

它完成了工作 - 我现在可以将表达式从第一种形式转换为第二种形式.但我有兴趣了解是否可以通过简单的方式实现这一点,而无需遍历表达式树.

kvb*_*kvb 6

我认为不可能做到这一点; 在第二种情况下,您将表达式<@ (fun (x : Pet) -> x.Name) @>(使用Lambda节点表示)插入另一个表达式的孔中.在此插入过程中,编译器不会简化表达式,因此Lambda无论您执行什么操作,都不会删除该节点.

但是,您的模式匹配解决方法可以大大简化:

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

事实上,您更复杂的版本是不正确的.这是因为delegateArg最里面的模式delegateArg与外部模式中先前绑定的标识符的值不匹配; 它是一个新的,新近绑定的标识符,也恰好被称为delegateArg.实际上,外部delegateArg标识符具有类型,Var list而内部标识符具有类型Expr!但是,鉴于编译器生成的表达形式范围有限,您的破解版本在实践中可能不会有问题.

编辑

关于你的后续问题,如果我理解正确,可能无法实现你想要的.不同于C#,其中x => x + 1可以被解释为具有一种类型的任一Func<int,int>Expression<Func<int,int>>,在F#fun x -> x + 1的类型为总是int->int.如果要获取类型值,Expr<int->int>则通常需要使用引号运算符(<@ @>).

但是,有一种替代方案可能有用.您可以[<ReflectedDefinition>]在let bound函数上使用该属性来使其引用也可用.这是一个例子:

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) @>
Run Code Online (Sandbox Code Playgroud)