可以使用F#报价来创建适用于任意F#记录类型的函数吗? [英] Can F# Quotations be used to create a function applicable to arbitrary F# record types?

查看:108
本文介绍了可以使用F#报价来创建适用于任意F#记录类型的函数吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

给出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#引号定义的方法:

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

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

有行为:

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

以某种方式使(><)适用于任意F#记录类型,不仅适用于 R.

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

:在给定任意记录类型和适用于其字段的补码功能(-?>)的情况下,是否可以使用引号即时生成(><)定义的F#代码?

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?

推荐答案

您可以使用F#引用为每个特定记录构造一个函数,然后使用F#PowerPack中提供的引用编译器对其进行编译.但是,如评论中所述,使用F#反射肯定更容易:

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)

此函数获取记录,动态获取其字段,然后将f应用于字段.您可以像这样使用它来实现您的运算符(我使用的是名称可读的函数):

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 

使用Reflection的解决方案很容易编写,但效率可能较低.每次调用函数applyOnFields时,都需要运行.NET Reflection.如果知道记录类型,则可以使用引号来构建一个AST,该AST表示可以手工编写的函数.像这样:

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)

建立正确的报价后,可以使用F#PowerPack对其进行编译.请参见此代码段的示例.

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

这篇关于可以使用F#报价来创建适用于任意F#记录类型的函数吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆