使用F#生成类型提供程序类型作为泛型类型参数

Wal*_*lly 9 f# type-providers

背景

我正在学习生成型提供者.

在这里这里使用了Cameron Taggart的VectorTP示例.在该代码中,他为具有设计时指定数量的属性的向量类构建C#代码,编译它并返回生成的类型.它运作良好.

例如,此客户端代码编译并运行:

type Vector2D = Vector<"X", "Y">
let v1 = Vector2D()
v1.X <- 3.14
v1.Y <- 2.91
Run Code Online (Sandbox Code Playgroud)

如果你在设计时查看类型提供者代码内部的内容,你会看到类型提供者代码被调用如下:

ITypeProvider.ApplyStaticArguments(
    VectorTP.Vector,                       // typeWithoutArguments
    [|"ConsoleApplication8"; "Vector2D"|], // typeNameWithArguments
    [|"X"; "Y"; ""; ""; ""; ""; ""|])      // staticArguments
Run Code Online (Sandbox Code Playgroud)

一切看起来都不错.

问题

客户端代码无法编译:

type Vector2D = Vector<"X", "Y">
let list = System.Collections.Generic.List<Vector2D>()
Run Code Online (Sandbox Code Playgroud)

这一次,如果您在设计时查看类型提供程序代码内部的内容,则在将其添加到客户端代码时会看到此附加调用List<Vector2D>:

ITypeProvider.ApplyStaticArguments(
    Mindscape.Vectorama.Vector2D,   // typeWithoutArguments
    [|"Vector2D,"|],                // typeNameWithArguments
    [|""; ""; ""; ""; ""; ""; ""|]) // staticArguments
Run Code Online (Sandbox Code Playgroud)

似乎类型提供程序框架(正确的术语?)正在调用ITypeProvider.ApplyStaticArguments基于Vector2D没有任何静态参数的生成类型.但是,Vector2D已经是生成的类型?!

VectorTP示例未正确处理此情况,因此客户端代码将无法编译.

注意

我确实尝试将type Vector2D = Vector<"X", "Y">声明移动到单独的DLL中,然后引用该DLL.当然,这是按预期工作的.生成的Vector2D类在该点看起来就像任何其他类型.

复杂性似乎是生成类型并将其用作同一程序集中的泛型参数(或脚本,通过我没有尝试过).

问题

  • 这是"类型提供程序框架"中的问题吗?或者这是预期的行为?

  • ApplyStaticArguments当我使用生成的类型作为泛型类型参数时,为什么被调用?

  • 如果ITypeProvider应该处理这种情况,那么适当的反应是什么?

Wal*_*lly 7

阅读完评论并做更多实验后,这就是我的结论.

1.这是"类型提供者框架"中的问题吗?或者这是预期的行为?

这不是预期的行为.

当您尝试将生成的类型用作泛型类型参数时,问题才会变得明显.当您将生成的类型用作泛型类型参数时,框架将调用ITypeProvider.GetStaticParameters该生成的类型.目前还不清楚为什么需要这样做.

无论如何,由于这个意外的调用,执行ITypeProvider.GetStaticParameters()不能像这样简单:

member this.GetStaticParameters(typeWithoutArguments) =
    [1..7] |> List.map (fun i -> stringParameter i "") |> List.toArray
Run Code Online (Sandbox Code Playgroud)

必须是这样的:

member this.GetStaticParameters(typeWithoutArguments) =
    if typeWithoutArguments = typeof<Vector> then
        [1..7] |> List.map (fun i -> stringParameter i "") |> List.toArray
    else
        [||] // for the generated types like Vector2D
Run Code Online (Sandbox Code Playgroud)

完成上述更改后,我能够编译使用该客户端的客户端代码List<Vector2D>.

2.当我使用生成的类型作为泛型类型参数时,为什么要调用ApplyStaticArguments?

请注意,我原来问题的重点是为什么ITypeProvider.ApplyStaticArguments被调用.ApplyStaticArguments被调用的原因是因为GetStaticParameters说生成的类型(Vector2D)本身需要静态参数.修复后GetStaticParameters,ApplyStaticArguments不再调用生成的类型.现在这是有道理的.

3.如果ITypeProvider应该处理这种情况,那么正确的响应是什么?

已经在上面提到了.然而,它提出了一个更大的问题,"生成型提供者的一个好例子在哪里?" 这个问题已经被问到了,但这里没有回答.

最后,关于ProvideTypes.fs

在评论中,有一个问题是使用该ProvidedTypes.fs库是否可以解决这个问题.我重复这个练习ProvidedTypes.fs并且最初遇到了同样的问题.也就是说,只要我在客户端代码中使用生成的类型作为泛型类型参数,客户端代码就不会编译.即使ProvidedTypes.fs你必须认识到你的ProvidedTypeDefinition.DefineStaticParameters(相当于ITypeProvider.GetStaticParameters)方法的处理程序必须考虑到它可能被称为传递你生成的类型的事实.