在尝试让类型提供程序生成更多惯用代码时,我开始考虑从提供程序返回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#代码。
有没有人知道是否有办法做到这一点?
答案 0 :(得分:5)
我认为构建具有动态类型的curried函数(取决于某些输入)可能是你必须使用各种Expr
构造函数手动构建引号的情况。
我想这可能是与您printf type provider相关的问题,因此我将其作为灵感。以下函数采用格式说明符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!