Can F# Quotations be used to create a function applicable to arbitrary F# record types?

StackOverflow https://stackoverflow.com/questions/9249409

  •  28-04-2021
  •  | 
  •  

문제

Given an F# record:

type R = { X : string ; Y : string }

and two objects:

let  a = { X = null ; Y = "##" }
let  b = { X = "##" ; Y = null }

and a predicate on strings:

let (!?) : string -> bool = String.IsNullOrWhiteSpace

and a function:

let (-?>) : string -> string -> string = fun x y -> if !? x then y else x

is there a way to use F# quotations to define:

let (><) : R -> R -> R

with behaviour:

let c = a >< b // = { X = a.X -?> b.X ; Y = a.Y -?> b.Y }

in a way that somehow lets (><) work for any arbitrary F# record type, not just for R.

Short: Can quotations be used to generate F# code for a definition of (><) on the fly given an arbitrary record type and a complement function (-?>) applicable to its fields?

If quotations cannot be used, what can?

도움이 되었습니까?

해결책

You could use F# quotations to construct a function for every specific record and then compile it using the quotation compiler available in F# PowerPack. However, as mentioned in the comments, it is definitely easier to use F# reflection:

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)

This function takes records, gets their fields dynamically and then applies f to the fields. You can use it to imiplement your operator like this (I'm using a function with a readable name instead):

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 

The solution using Reflection is quite easy to write, but it might be less efficient. It requires running .NET Reflection each time the function applyOnFields is called. You could use quotations to build an AST that represents the function that you could write by hand if you knew the record type. Something like:

let applyOnFields (a:R) (b:R) f = { X = f (a.X, b.X); Y = f (a.Y, b.Y) }

Generating the function using quotations is more difficult, so I won't post a complete sample, but the following example shows at least a part of it:

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)

Once you build the right quotation, you can compile it using F# PowerPack. See for example this snippet.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top