【问题标题】:Can F# Quotations be used to create a function applicable to arbitrary F# record types?可以使用 F# 引号创建适用于任意 F# 记录类型的函数吗?
【发布时间】:2012-02-12 12:30:50
【问题描述】:

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

以某种方式让(&gt;&lt;) 适用于任何任意 F# 记录类型,不仅仅适用于 R

简短:在给定任意记录类型和适用于其字段的补码函数(-?&gt;) 的情况下,能否使用引号为(&gt;&lt;) 的定义动态生成F# 代码?

如果不能用引号,那还能用什么?

【问题讨论】:

  • 我认为你必须使用反射来做这样的事情。
  • @svick 我知道。如果碰巧存在的话,我只是对实现这一目标的其他方法感兴趣。尤其是一些会导致 F# 代码生成的代码。
  • 我看到使用 .NET 执行此操作的唯一方法是反射(正如 @svick 所建议的那样)。您还可以使用 F# 宏为您需要的每条记录自动创建函数。
  • @RamonSnir - 需要明确的是,F# 2.0 没有宏系统,我认为您是在暗指自己的工作:ramon.org.il/wp/2011/12/text-based-macro-system-for-f
  • @StephenSwensen 是的,我是。 .NET 本身(包括 F#)不支持任何好的解决方案。

标签: f# records quotations


【解决方案1】:

您可以使用 F# 引用为每个特定记录构造一个函数,然后使用 F# PowerPack 中提供的引用编译器对其进行编译。但是,正如 cmets 中提到的,使用 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# 反射这么简单。顺便说一句,第二部分正是我希望听到的。我很想学习引文,让他们为我做代码生成,而不是我自己打字。听到这是可能的消息令人鼓舞:)
  • @CetinSert 最后一点是,使用 F# 引号生成代码时需要小心。编译首先将引用转换为 LINQ 表达式树,然后对其进行编译。所以编译后的代码不会像普通的 F# 代码那样高效。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多