生成参数化 F# 引用

发布于 2024-09-12 13:56:30 字数 2056 浏览 0 评论 0 原文

假设我们有一个简单的 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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

¢好甜 2024-09-19 13:56:30

我认为这是不可能的;在第二种情况下,您插入表达式 <@ (fun (x : Pet) -> x.Name) @>,它使用 Lambda 表示code> 节点,插入其他表达式的洞中。在此插入过程中,编译器不会简化表达式,因此无论您做什么,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 =>在 F# 中,x + 1 可以解释为具有 FuncExpression> 类型有趣的x-> x + 1 始终为 int->int 类型。如果要获取 Exprint> 类型的值,那么通常需要使用引号运算符 (<@ @>)

然而,有一种替代方案可能有用。您可以在 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) @>

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 a Lambda node, into the hole in the other expression. The compiler does not simplify expressions during this plugging process, so the Lambda node won't be removed no matter what you do.

However your pattern matching workaround can be greatly simplified:

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

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 bound delegateArg identifier from the outer pattern; it is a new, freshly bound identifier which also happens to be called delegateArg. In fact, the outer delegateArg identifier has type Var list while the inner one has type Expr! 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 either Func<int,int> or Expression<Func<int,int>>, in F# fun x -> x + 1 is always of type int->int. If you want to get a value of type Expr<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:

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) @>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文