Cet*_*ert 2 f# records quotations
鉴于F#记录:
type R = { X : string ; Y : string }
Run Code Online (Sandbox Code Playgroud)
和两个对象:
let a = { X = null ; Y = "##" }
let b = { X = "##" ; Y = null }
Run Code Online (Sandbox Code Playgroud)
和字符串的谓词:
let (!?) : string -> bool = String.IsNullOrWhiteSpace
Run Code Online (Sandbox Code Playgroud)
和功能:
let (-?>) : string -> string -> string = fun x y -> if !? x then y else x
Run Code Online (Sandbox Code Playgroud)
有没有办法使用F#语录来定义:
let (><) : R -> R -> R
Run Code Online (Sandbox Code Playgroud)
行为:
let c = a >< b // = { X = a.X -?> b.X ; Y = a.Y -?> b.Y }
Run Code Online (Sandbox Code Playgroud)
以某种方式让(><)任何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)
Run Code Online (Sandbox Code Playgroud)
此函数记录记录,动态获取其字段,然后应用于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
Run Code Online (Sandbox Code Playgroud)
使用Reflection的解决方案很容易编写,但可能效率较低.每次applyOnFields调用函数时都需要运行.NET Reflection .如果您知道记录类型,可以使用引号构建一个AST,表示您可以手动编写的函数.就像是:
let applyOnFields (a:R) (b:R) f = { X = f (a.X, b.X); Y = f (a.Y, b.Y) }
Run Code Online (Sandbox Code Playgroud)
使用引号生成函数更加困难,因此我不会发布完整的示例,但以下示例至少显示了其中的一部分:
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)
Run Code Online (Sandbox Code Playgroud)
构建正确的引用后,可以使用F#PowerPack进行编译.例如,请参阅此代码段.