F# 引用可以用于创建适用于任意 F# 记录类型的函数吗?

发布于 2025-01-04 23:07:38 字数 819 浏览 1 评论 0原文

给定一个 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

:给定任意记录类型和补码函数 ( -?>) 适用于其字段吗?

如果不能使用引用,那什么可以呢?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

半葬歌 2025-01-11 23:07:38

您可以使用 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 

使用反射的解决方案非常容易编写,但效率可能较低。每次调用函数 applyOnFields 时,都需要运行 .NET Reflection。如果您知道记录类型,您可以使用引号来构建表示您可以手写的函数的 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 对其进行编译。请参阅此代码段示例

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文