2012-02-12 83 views
2

给定一个F#记录:是否可以使用F#语句创建适用于任意F#记录类型的函数?

type R = { X : string ; Y : string } 

和两个对象:

let a = { X = null ; Y = "##" } 
let b = { X = "##" ; Y = null } 

和字符串谓词:

let (!?) : string -> bool = String.IsNullOrWhiteSpace 

和功能:

let (-?>) : string -> string -> string = fun x y -> if !? x then y else x 

是有没有办法使用F#报价定义:

let (><) : R -> R -> R 

与行为:

let c = a >< b // = { X = a.X -?> b.X ; Y = a.Y -?> b.Y } 
的方式,在某种程度上可以对任意的F#记录类型 (><)工作, 不仅仅是 R

:能否报价被用来生成F#代码给出一个任意记录类型和补充功能(-?>)适用于其领域的(><)对飞一个定义?

如果报价不能使用,有什么可以?

+0

我认为你将不得不使用反射做这样的事情。 – svick 2012-02-12 13:19:49

+0

@svick我知道。如果碰巧存在,我只是想通过其他方式来实现这一点。尤其是一些导致F#代码生成的结果。 – 2012-02-12 13:27:29

+0

我看到用.NET做这件事的唯一方法就是反射(就像@svick所建议的那样)。您也可以使用F#宏为您需要的每个记录自动创建函数。 – 2012-02-12 13:58:38

回答

6

您可以使用F#语句为每个特定记录构造一个函数,然后使用F#PowerPack中提供的引用编译器对其进行编译。然而,由于在评论中提到的,它肯定是更容易使用F#反射:

open Microsoft.FSharp.Reflection 

let applyOnFields (recd1:'T) (recd2:'T) f = 
    let flds1 = FSharpValue.GetRecordFields(recd1) 
    let flds2 = FSharpValue.GetRecordFields(recd2) 
    let flds = Array.zip flds1 flds2 |> Array.map f 
    FSharpValue.MakeRecord(typeof<'T>, flds) 

这个函数有记录,动态地得到他们的领域,然后应用f到田间地头。你可以用它来imiplement您的运营商像这样(我使用的功能有可读的名称代替):

type R = { X : string ; Y : string } 
let a = { X = null ; Y = "##" } 
let b = { X = "##" ; Y = null } 

let selectNotNull (x:obj, y) = 
    if String.IsNullOrWhiteSpace (unbox x) then y else x 

let c = applyOnFields a b selectNotNull 

该解决方案使用反射很容易写,但也可能是低效率的。每次调用函数applyOnFields时,都需要运行.NET Reflection。如果您知道记录类型,则可以使用引号来构建代表您可以手动编写的函数的AST。喜欢的东西:

let applyOnFields (a:R) (b:R) f = { X = f (a.X, b.X); Y = f (a.Y, b.Y) } 

使用生成的报价是比较困难的,所以我将不会发布一个完整样本的功能,但下面的例子显示,至少它的一部分:

open Microsoft.FSharp.Quotations 

// Get information about fields 
let flds = FSharpType.GetRecordFields(typeof<R>) |> List.ofSeq 

// Generate two variables to represent the arguments 
let aVar = Var.Global("a", typeof<R>) 
let bVar = Var.Global("b", typeof<R>) 

// For all fields, we want to generate 'f (a.Field, b.Field)` expression 
let args = flds |> List.map (fun fld -> 
    // Create tuple to be used as an argument of 'f' 
    let arg = Expr.NewTuple [ Expr.PropertyGet(Expr.Var(aVar), fld) 
          Expr.PropertyGet(Expr.Var(bVar), fld) ] 
    // Call the function 'f' (which needs to be passed as an input somehow) 
    Expr.App(???, args) 

// Create an expression that builds new record 
let body = Expr.NewRecord(typeof<R>, args) 

一旦你建立正确的引用,你可以使用F#PowerPack编译它。请参阅example this snippet

+0

不知道F#反射太容易了。顺便说一下,第二部分正是我希望听到的。我很乐意学习语言,让他们为我做代码而不是我自己打字。听到这可能是令人鼓舞的:) – 2012-02-13 08:09:59

+0

@CetinSert最后一点是,你需要小心使用F#语句生成代码。编译首先将引用转换为LINQ表达式树,然后编译它。所以编译后的代码不会像普通的F#代码那样高效。 – 2012-02-13 16:00:27

相关问题