6

假设我们有一个简单的 F# 引用:

类型宠物 = { 名称:字符串 }
让 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(一些(x),System.String名称,[]))

现在我想概括它,所以我可以使用在其上定义的任意类型和方法/属性,而不是输入“Pet”和属性“Name”。这是我正在尝试做的事情:

让 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]],
             委托Arg,
             应用程序(拉姆达(X,
                                  PropertyGet(一些(x),System.String名称,[])),
                          委托Arg))

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

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

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

让 convertExpr (expr : Expr) =
    匹配 expr
    | NewDelegate(t, darg, appl) ->
        匹配 (darg, appl) 与
        | (delegateArg, appl) ->
            匹配应用程序
            | 应用程序(l,ldarg)->
                匹配 (l, ldarg) 与
                | (Lambda(x, f), delegateArg) ->
                    Expr.NewDelegate(t, [x], f)
                | _ -> 表达式
            | _ -> 表达式
    | _ -> 表达式

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

4

1 回答 1

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!但是,鉴于编译器生成的表达式形式范围有限,您的损坏版本在实践中可能不会有问题。

编辑

关于您的后续问题,如果我对您的理解正确,则可能无法实现您想要的。与 C# 不同, wherex => x + 1可以解释为类型为Func<int,int>or Expression<Func<int,int>>,在 F#fun x -> x + 1中,类型始终为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) @>
于 2010-08-05T16:22:58.787 回答