Search code examples
f#curryingtype-providers

How can I build an arbitary curried function in an F# type provider?


In trying to get type providers to generate more idiomatic code, I've started looking into returning curried functions from the provider.

This little snippet of code:

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)

Seems to work for providing an int -> int -> string if I know the desired signature in advance; but ideally I'd like to build the nested lambda functions up dynamically, something like this:

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

Unfortunately, the above isn't valid F# code due to the conflicting return types of the quotations.

Does anyone know if there is a way to do this?


Solution

  • I think that building curried functions with dynamic type (depending on some input) is probably a case where you have to resort to building the quotation by hand using the various Expr constructors.

    I guess this might be a question related to your printf type provider, so I used that as an inspiration. The following function takes a list of format specifiers args which can contain either "s" for strings or "n" for integers. Given e.g. ['s'; 'n'], it builds a function string -> (int -> string) that formats the first two arguments and returns a concatenated string with the results:

    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])
    

    I have not tried this in the type provider context, but it can be compiled and evaluated:

    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!