您可以使用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。
我認爲你將不得不使用反射做這樣的事情。 – svick 2012-02-12 13:19:49
@svick我知道。如果碰巧存在,我只是想通過其他方式來實現這一點。尤其是一些導致F#代碼生成的結果。 – 2012-02-12 13:27:29
我看到用.NET做這件事的唯一方法就是反射(就像@svick所建議的那樣)。您也可以使用F#宏爲您需要的每個記錄自動創建函數。 – 2012-02-12 13:58:38