[英]Generating parameterized 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
它完成了工作 - 我现在可以将表达式从第一种形式转换为第二种形式。 但我有兴趣了解是否可以通过简单的方式实现这一点,而无需遍历表达式树。
我认为不可能做到这一点; 在第二种情况下,您将表达式<@ (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#不同,其中x => x + 1
可以解释为具有Func<int,int>
或Expression<Func<int,int>>
,在F# fun x -> x + 1
中始终为int->int
类型int->int
。 如果要获取Expr<int->int>
类型的值,则通常需要使用引号运算符(<@ @>)
。
但是,有一种替代方案可能有用。 您可以在let绑定函数上使用[<ReflectedDefinition>]
属性来使其引用也可用。 这是一个例子:
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) @>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.