如何在F#类型的提供程序中构建任意的curried函数



在试图让类型提供程序生成更惯用的代码时,我已经开始研究从提供程序返回curried函数。

这一小段代码:

let lambdaTest () =
    let inner =
        <@ fun myInt ->
                fun int2 -> sprintf "A string! %d %d" myInt int2 @>
    let innerType = inner.GetType()
    ProvidedProperty(
        "lambda", 
        innerType.GenericTypeArguments.[0], 
        IsStatic = true, 
        GetterCode = fun _ -> inner.Raw)

如果我事先知道想要的签名,似乎可以提供int -> int -> string;但理想情况下,我想动态地构建嵌套的lambda函数,比如这样的:

let rec inline curry<'a> format (func: Quotations.Expr<'a>) : Quotations.Expr<'a> =
    match format with
    | FString f ->
        curry<string -> 'a> f <@ fun (s : str) -> %func @>
    | FInt f ->
        curry<int -> 'a> f <@ fun (i: int) -> %func @>
    | Other (_, f) ->
        curry<'a> f func
    | End ->
        func

不幸的是,由于引号的返回类型冲突,上面的F#代码无效。

有人知道是否有办法做到这一点吗?

我认为构建具有动态类型的curried函数(取决于一些输入)可能是一种必须使用各种Expr构造函数手动构建报价的情况。

我想这可能是一个与你的printf类型提供者有关的问题,所以我用它作为灵感。以下函数获取格式说明符args的列表,其中可以包含字符串的"s",也可以包含整数的"n"。例如,给定['s'; 'n'],它构建一个函数string -> (int -> string),格式化前两个参数,并返回一个带有结果的串联字符串:

open Microsoft.FSharp.Quotations
let rec buildFunc args printers = 
  match args with
  | 's'::args ->
      // Build a function `string -> (...)` where the `(...)` part is function
      // or value generated recursively based on the remaining `args`.
      let v = Var("v", typeof<string>)
      let printer = <@@ "Str: " + (%%(Expr.Var v)) + "n" @@>
      // As we go, we accumulate a list of "printers" which are expressions of
      // type `string` that return the variables we are building, formatted...
      Expr.Lambda(v, buildFunc args (printer::printers))
  | 'n'::args ->
      // Pretty much the same, but we use `string<int>` to convert int to string
      let v = Var("v", typeof<int>)
      let printer = <@@ "Num: " + (string<int> (%%(Expr.Var v))) + "n" @@>
      Expr.Lambda(v, buildFunc args (printer::printers))
  | [] ->      
      // Builds: String.Format [| f1; f2; f3 |] where 'f_i' are the formatters
      let arr = Expr.NewArray(typeof<string>, List.rev printers)
      let conc = typeof<string>.GetMethod("Concat", [|typeof<string[]>|])
      Expr.Call(conc, [arr])

我没有在类型提供程序上下文中尝试过这一点,但它可以编译和评估:

open Microsoft.FSharp.Linq.RuntimeHelpers.LeafExpressionConverter
// Generate 'int -> (string -> (int -> string))'
let fe = buildFunc (List.ofSeq "nsn") []
fe.Type.FullName // Shows the right type in a bit ugly way
// Evaluate the expression & cast to a function type
let f = (EvaluateQuotation(fe) :?> (int -> (string -> (int -> string)))) 
f 1 "a" 2 // Works!

最新更新