F#Type Provider开发:提供方法时,如何访问变量和类型的参数?

Ove*_*urg 3 f# type-providers

我的问题是:

当我在设计时不知道这些表达式的数量和类型时,如何将列表中的表达式拼接成引号?

在底部,我已经包含了类型提供程序的完整代码.(我已经删除了这个概念以证明问题.)我的问题发生在以下几行:

  let func = ProvidedMethod((*...*), InvokeCode = fun args ->
        <@@ let stringParts =
                args
                |> List.mapi (fun i arg -> 
                    if paramTypes.[i] = typeof&lt;int&gt; then
                        sprintf "%i" (%%arg: int)...
Run Code Online (Sandbox Code Playgroud)

在lambda参数上arg,我收到以下错误:

error FS0446: The variable 'arg' is bound in a quotation but is used as part of a spliced expression. This is not permitted since it may escape its scope.``
Run Code Online (Sandbox Code Playgroud)

我无法弄清楚如何编写代码,以便在提供者设计时不知道值的数量和类型时"提取"参数值(虽然它们将在编译时知道).

当我在设计时知道参数的存在和类型时,我可以这样做:

printfn "%A" (%%args.[0]: int)

但我无法弄清楚如何从Expr list输入到obj list引号内.

这是完整类型的提供者代码:

[<TypeProvider>]
type SillyProviderDefinition(config: TypeProviderConfig) as self = 
    inherit TypeProviderForNamespaces()

let sillyType = ProvidedTypeDefinition(THIS_ASSEMBLY, NAMESPACE, "SillyProvider", Some typeof<obj>)
    do sillyType.DefineStaticParameters([ProvidedStaticParameter("argTypes", typeof<string>)], fun typeName args ->
        let retType = ProvidedTypeDefinition(typeName, Some typeof<obj>)        

        let paramTypes =
            (args.[0] :?> string).Split([|'|'|])
            |> Array.map (function
                | "int" -> typeof<int>
                | "string" -> typeof<string>
                | x -> failwithf "Invalid argType %A. Only string or int permitted" x)
        let parameters =
            paramTypes
            |> Array.mapi (fun i p -> ProvidedParameter(sprintf "arg%i" i, p))
            |> Array.toList

        let func = ProvidedMethod("Stringify", parameters, typeof<string>, IsStaticMethod = true, InvokeCode = fun args ->
            <@@ let stringParts =
                    args
                    |> List.mapi (fun i arg ->
                        if paramTypes.[i] = typeof<int> then
                            sprintf "%i" (%%arg: int)
                        elif paramTypes.[i] = typeof<string> then
                            (%%arg: string)
                        else
                            failwith "Unexpected arg type")
                //printfn "%A" (%%args.[0]: int)
                String.Join("", stringParts) @@>)

        do retType.AddMember func
        do sillyType.AddMember retType
        retType)

    do self.AddNamespace(NAMESPACE, [sillyType])
Run Code Online (Sandbox Code Playgroud)

Tom*_*cek 5

作为一个最小的例子,假设我们有类型列表和带有引号的列表(在类型提供程序的上下文中,您有类型列表,并且args是引用列表,可能还包含this实例):

open Microsoft.FSharp.Quotations

let tys = [ typeof<int>; typeof<string> ]
let args = [ Expr.Value(42); Expr.Value("test"); ]
Run Code Online (Sandbox Code Playgroud)

我们想要构造一个调用formatIntformatString取决于类型的表达式,然后连接所有格式化的字符串:

let formatInt (n:int) = string n
let formatString (s:string) = s
Run Code Online (Sandbox Code Playgroud)

现在,区分所提供的引用代码(引用级别)和生成引用(代码级别)的普通代码中的内容非常重要.在代码级,我们遍历所有类型和引用的参数,并生成一个带有调用的引用列表formatIntformatString- 可以键入Expr<string>它们,因为它们具有相同的类型:

let formattedArgList = 
  [ for t, e in List.zip tys args ->
      if t = typeof<int> then <@ formatInt %%e @>
      elif t = typeof<string> then <@ formatString %%e @>
      else failwith "!" ]
Run Code Online (Sandbox Code Playgroud)

现在,您可以通过fold在代码级调用并::在引用级别使用list 运算符来构建列表表达式:

let listArgExpr = 
  formattedArgList
  |> List.fold (fun state e -> <@ %e::%state @>) <@ [] @>
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用引用来构建报价String.concat:

<@ String.concat "," %listArgExpr @>
Run Code Online (Sandbox Code Playgroud)