Можно ли использовать цитаты F # для создания функции, применимой к произвольным типам записей F #? - PullRequest
2 голосов
/ 12 февраля 2012

Учитывая запись 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.

Short : можно ли использовать цитаты для генерации кода F # для определения (><) вмухе дан произвольный тип записи и функция дополнения (-?>), применимая к ее полям?

Если цитаты не могут быть использованы, что может?

1 Ответ

6 голосов
/ 12 февраля 2012

Вы можете использовать цитаты 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. См. пример этого фрагмента .

...