假设我们有一个简单的 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,而第二个表达式将 PropertyGet 包装在应用程序/Lambda 表达式。当我将此表达式传递给外部代码时,它不期望这样的表达式结构并失败。
因此,我需要某种方法来构建引用的通用版本,因此当它变得专业化时,生成的引用与 <@@ 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
它完成了工作 - 我现在可以将表达式从第一种形式转换为第二种形式。但我有兴趣了解是否可以通过简单的方式实现这一点,而无需遍历表达式树。
Let's say we have a simple F# quotation:
type Pet = { Name : string }
let exprNonGeneric = <@@ System.Func(fun (x : Pet) -> x.Name) @@>
The resulting quotation is like:
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 I instead of type "Pet" and property "Name" I could use an arbitrary type and 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 the second expression is that in first case the top level NewDelegate expression contains PropertyGet, while the second expression wraps PropertyGet in a Application/Lambda expression. And when I pass this expression to an external code it does not expect such expression structure and fails.
So I need some way to build a generalized version of quotation, so when it gets specialized, the resulting quotation is an exact match of <@@ System.Func(fun (x : Pet) -> x.Name) @@>. Is this possible? Or it there only choice to manually apply pattern matching to a generated quotation and transform 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
It does the job - I can now convert expression from 1st to 2nd form. But I am interested in finding out if this can be achieved in a simple way, without traversing expression trees.
发布评论
评论(1)
我认为这是不可能的;在第二种情况下,您插入表达式
<@ (fun (x : Pet) -> x.Name) @>
,它使用Lambda
表示code> 节点,插入其他表达式的洞中。在此插入过程中,编译器不会简化表达式,因此无论您做什么,Lambda
节点都不会被删除。但是,您的模式匹配解决方法可以大大简化:
事实上,您更复杂的版本是不正确的。这是因为最里面模式中的
delegateArg
与外部模式中之前绑定的delegateArg
标识符的值不匹配;它是一个新绑定的新标识符,也称为delegateArg
。事实上,外部delegateArg
标识符的类型为Var list
,而内部标识符的类型为Expr
!然而,考虑到编译器生成的表达式形式的范围有限,您的损坏版本在实践中可能不会有问题。编辑
关于你的后续问题,如果我理解正确的话,可能无法实现你想要的。与 C# 不同,其中
x =>在 F# 中,x + 1
可以解释为具有Func
或Expression>
类型有趣的x-> x + 1
始终为int->int
类型。如果要获取Exprint>
类型的值,那么通常需要使用引号运算符(<@ @>)
。然而,有一种替代方案可能有用。您可以在 let 绑定函数上使用
[]
属性来使其引用也可用。这是一个例子:I don't think it will be possible to do this; in the second case, you are plugging in the expression
<@ (fun (x : Pet) -> x.Name) @>
, which is represented using aLambda
node, into the hole in the other expression. The compiler does not simplify expressions during this plugging process, so theLambda
node won't be removed no matter what you do.However your pattern matching workaround can be greatly simplified:
In fact, your more complicated version is incorrect. This is because the
delegateArg
in your innermost pattern is not matching against the value of the previously bounddelegateArg
identifier from the outer pattern; it is a new, freshly bound identifier which also happens to be calleddelegateArg
. In fact, the outerdelegateArg
identifier has typeVar list
while the inner one has typeExpr
! However, given the limited range of expression forms generated by the compiler your broken version may not be problematic in practice.EDIT
Regarding your followup questions, if I understand you correctly it may not be possible to achieve what you want. Unlike C#, where
x => x + 1
could be interpreted as having a type of eitherFunc<int,int>
orExpression<Func<int,int>>
, in F#fun x -> x + 1
is always of typeint->int
. If you want to get a value of typeExpr<int->int>
then you generally need to use the quotation operator(<@ @>)
.There is one alternative that may be of use, however. You can use the
[<ReflectedDefinition>]
attribute on let bound functions to make their quotations available as well. Here's an example: