生成参数化F#报价

时间:2010-08-05 06:45:41

标签: 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。当我将这个表达式传递给外部代码时,它不会期望这样的表达式结构并失败。

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

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

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

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

1 个答案:

答案 0 :(得分: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

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

修改

关于你的后续问题,如果我理解正确,可能无法达到你想要的效果。与C#不同,其中Func<int,int>可以被解释为具有Expression<Func<int,int>>fun x -> x + 1类型,在F#int->int中始终为Expr<int->int>类型。如果您想获得(<@ @>)类型的值,则通常需要使用引用运算符[<ReflectedDefinition>]

然而,有一种替代方案可能有用。您可以在let绑定函数上使用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) @> 属性来使其引用也可用。这是一个例子:

{{1}}