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

倾然丶 夕夏残阳落幕 提交于 2019-12-24 01:47:34

问题


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?


回答1:


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.



来源:https://stackoverflow.com/questions/9248767/can-f-quotations-be-used-to-create-a-function-applicable-to-arbitrary-f-record

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!