2012-02-12 84 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

相關問題