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
から、フィールドに適用します。これを使用して、次のように演算子を実装できます(代わりに、読み取り可能な名前の関数を使用しています)。
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
Reflectionを使用したソリューションは非常に簡単に記述できますが、効率が低下する可能性があります。applyOnFields
関数が呼び出されるたびに.NETReflectionを実行する必要があります。レコードタイプがわかっている場合は、引用符を使用して、手動で記述できる関数を表す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を使用してコンパイルできます。たとえば、このスニペットを参照してください。