Вы можете использовать цитаты 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, довольно легко написать, но оно может быть менее эффективным. Требуется запуск .NET Reflection каждый раз, когда вызывается функция applyOnFields
. Вы можете использовать цитаты для построения 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. См. пример этого фрагмента .