f# - F# Quotations 能否用于创建适用于任意 F# 记录类型的函数?

标签 f# records quotations

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

简短:可以使用引号为 (><) 的定义生成 F# 代码吗?即时给定任意记录类型和补函数 (-?>)适用于其领域?

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

最佳答案

您可以使用 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 对其进行编译。查看 example this snippet .

关于f# - F# Quotations 能否用于创建适用于任意 F# 记录类型的函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9248767/

相关文章:

php - 为什么我不能在 php mysql 中显示我的记录?

linq - 参数 LINQ 查询

list - 与 List 类型匹配的 FSharp 模式

F#,管道转发第一个参数

具有记录和类类型的 Haskell 多态函数

php - 多用户应用程序记录锁定 - 最佳方法?

http - F# HTTP Post,读取状态码响应

asynchronous - 使用 F# 和 Task<T> 的循环并发算法

data-structures - 为什么 100 个函数对一种数据结构进行操作比 10 个函数对 10 个数据结构进行操作更好

f# - 构建代码表达式 "Manually"