将第一个类函数参数传递给LINQ表达式

时间:2018-05-06 06:19:36

标签: f#

此代码段再现了我使用某些生产代码时遇到的问题。函数containsProperty表示实际上在库中的真实世界函数,因此我对签名没有任何说法。

问题在于我无法弄清楚如何创建一个可以将普通函数作为参数的包装函数,然后将其传递给containsProperty。我可以使用函数作为lambda表达式直接调用containsProperty,但我无法使用来自其他源的函数调用它。

函数addToGroup是我迄今为止提出的最好的函数,它使用引号。这种方法存在两个问题,我想弄清楚。首先,我如何摆脱报价中的Func演员?也许以某种方式将其移动到addToGroup?第二,我可以在此基础上建立一个函数吗?我没有成功找到不会产生编译时错误或运行时错误的东西。

函数addToGroup2是我想要做的,但它不能编译。错误消息“没有构造函数可用于类型'Quotations.Expr<'a>'”。

为什么我要费心去做?因为只要我不能将传入函数视为第一类值,我就无法创建我所追求的设计。我希望这些函数来自一组记录。

如果将此代码段粘贴到LINQPad或其他内容中,请注释掉addToGroup2及其调用,以便使代码段编译并运行。

open System
open System.ComponentModel
open System.ComponentModel.DataAnnotations // Reference to this assembly required.

type CfgSettings = {
    mutable ConnectionString: string
    mutable Port: int
    }

and CfgSettingsMetadata() =
    static member containsProperty<'TProperty>(propertyExpression: Linq.Expressions.Expression<Func<CfgSettings,'TProperty>>) =
        Console.WriteLine "good!"
    static member addToGroup f =
        CfgSettingsMetadata.containsProperty(FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToLambdaExpression f) |> ignore
    static member addToGroup2 (f: CfgSettings -> 'TProperty) =
        CfgSettingsMetadata.containsProperty(FSharp.Linq.RuntimeHelpers.LeafExpressionConverter.QuotationToLambdaExpression (Quotations.Expr<Func<CfgSettings,'TProperty>>f)) |> ignore
    static member BuildMetadata () =
        CfgSettingsMetadata.containsProperty(fun x -> x.ConnectionString)
        CfgSettingsMetadata.containsProperty(fun x -> x.Port)
        CfgSettingsMetadata.addToGroup <@ Func<_,_>(fun x -> x.ConnectionString) @>
        CfgSettingsMetadata.addToGroup <@ Func<_,_>(fun x -> x.Port) @>
        CfgSettingsMetadata.addToGroup2 (fun x -> x.ConnectionString)
        CfgSettingsMetadata.addToGroup2 (fun x -> x.Port)

CfgSettingsMetadata.BuildMetadata()

问题Expression<Func<T, bool>> from a F# func中的两个答案对我有所帮助,但我还没有找到解决方案。

1 个答案:

答案 0 :(得分:5)

所以,这里有两个问题。

如何传递函数而不必将其包装在<@ ... @>

为此,您只需要将[<ReflectedDefinition>]属性添加到方法的参数中。它隐含地将引用的参数包装在引号中。

type CfgSettingsMetadata() =
    static member addToGroup([<ReflectedDefinition>] f: Expr<CfgSettings -> 'TProperty>) =
        CfgSettingsMetadata.containsProperty(LeafExpressionConverter.QuotationToLambdaExpression f) |> ignore

// Example use:
CfgSettingsMetadata.addToGroup(Func<_, _>(fun x -> x.ConnectionString))

如何从Expr<a -> b>转换为Expression<Func<a, b>>

这确实在您链接的问题中进行了解释,尽管从那时起API已经发生了一些变化。

type CfgSettingsMetadata() =
    static member addToGroup ([<ReflectedDefinition>] (f: Expr<CfgSettings -> 'TProperty>)) =
        let call = LeafExpressionConverter.QuotationToExpression f :?> MethodCallExpression
        let lambda = call.Arguments.[0] :?> LambdaExpression
        let e = Expression.Lambda<Func<CfgSettings, 'TProperty>>(lambda.Body, lambda.Parameters)
        CfgSettingsMetadata.containsProperty(e) |> ignore

// Example use:
CfgSettingsMetadata.addToGroup(fun x -> x.ConnectionString)