9
votes

Background

I am learning about generative type providers.

I used Cameron Taggart's VectorTP example from here and here. In that code he builds up the C# code for a vector class that has a design-time specified number of properties, compiles it, and returns the generated type. It worked well.

For example, this client code compiles and runs:

type Vector2D = Vector<"X", "Y">
let v1 = Vector2D()
v1.X <- 3.14
v1.Y <- 2.91

And if you look at what is going on inside the type provider code at design-time, you see that the type provider code is being called like this:

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

That all looks good.

Problem

This client code does not compile:

type Vector2D = Vector<"X", "Y">
let list = System.Collections.Generic.List<Vector2D>()

This time, if you look at what is going on inside the type provider code at design-time, you see this additional call when the List<Vector2D> is added to the client code:

ITypeProvider.ApplyStaticArguments(
    Mindscape.Vectorama.Vector2D,   // typeWithoutArguments
    [|"Vector2D,"|],                // typeNameWithArguments
    [|""; ""; ""; ""; ""; ""; ""|]) // staticArguments

It appears that the type provider framework (is that the right term?) is calling ITypeProvider.ApplyStaticArguments asking for a generated type based on Vector2D without any static arguments. But, Vector2D is already the generated type?!

The VectorTP example does not properly handle this case, and so the client code will not compile.

Note

I did try moving the type Vector2D = Vector<"X", "Y"> declaration into a separate DLL and then referencing that DLL. Of course, that worked as expected. The generated Vector2D class simply looks like any other type at that point.

The complication seems to be with generating the type and using it as generic parameter in the same assembly (or script, through I have not tried that).

Questions

  • Is this a problem in the "type provider framework"? Or is this the expected behavior?

  • Why is ApplyStaticArguments being called when I use the generated type as a generic type parameter?

  • If the ITypeProvider is supposed to handle this case, what is the proper response?

1
The type provider implementation looks a bit suspect... For instance, no matter what name it's given to resolve it always returns the same type, and likewise it always indicates that there are 6 static parameters, but it should only do that when the Vector type is actually the one being requested.kvb
@kvb - If you could give some more details, that would be good - I do not know much about generative type providers so I'm lost here - but I suspect most erasing providers just re-build types every time while here, the provider actually has to do some caching?Tomas Petricek
@kvb You noticed that VectorTP's implementation always returns the same type and same static parameters. Also notice that VectorTP's implementation of GetTypes() only advertises one type -- the Vector type. As a kind-of "hello-world-type-provider" I'm not surprised. What surprised me is that ApplyStaticArguments was called with the Vector2D type. I thought I understood the IProvidedNamespace and ITypeProvider interfaces until I observed this behavior.Wallace Kelly
Notice that code.google.com/p/froto/source/browse/Gen/ProtoTypeProvider.fs addresses this issue by throwing an exception if the framework asks for something other then the advertised type. The exception is on line 85, failwith "only ProtoGen supported". But that means that the generated type could similarly not be used in a List<ProtoGen>.Wallace Kelly
@TomasPetricek - I'd love to give more details, but it's been too long since I worked with generative providers for me to remember the specifics of what's expected. In general, using the raw API is extremely error-prone and unpleasant (which is unfortunate), so I'd stick to closely following what ProvidedTypes.fs does.kvb

1 Answers

7
votes

After reading the comments and doing more experimentation, this is my conclusion.

1. Is this a problem in the "type provider framework"? Or is this the expected behavior?

It is not the behavior I expected.

The problem only becomes apparent when when you try to use the generated type as a generic type parameter. When you use the generated type as a generic type parameter, the framework calls ITypeProvider.GetStaticParameters for that generated type. It's not yet clear to me why it would need to do this.

Regardless, because of this unexpected call, the implementation of ITypeProvider.GetStaticParameters() cannot be as simple as this:

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

It must be something like this:

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

After making the above change, I was able to compile client code that used the List<Vector2D>.

2. Why is ApplyStaticArguments being called when I use the generated type as a generic type parameter?

Notice that the focus of my original question was on why ITypeProvider.ApplyStaticArguments was being called. The reason ApplyStaticArguments was being called was because GetStaticParameters was saying that the generated type (Vector2D) itself required static parameters. After fixing GetStaticParameters, then ApplyStaticArguments was no longer being called for the generated type. That now makes sense.

3. If the ITypeProvider is supposed to handle this case, what is the proper response?

Already addressed above. However, it raises the bigger question of, "Where is a good example of a generative type provider?" That question was already asked, but not answered here.

Finally, regarding ProvidedTypes.fs

In the comments, there was a question about whether using the ProvidedTypes.fs library would solve this problem. I repeated this exercise using ProvidedTypes.fs and initially experienced the same problem. That is, as soon as I used the generated type as a generic type parameter in the client code, the client code would not compile. Even with ProvidedTypes.fs you must recognize that your handler for the ProvidedTypeDefinition.DefineStaticParameters (which is the equivalent of ITypeProvider.GetStaticParameters) method must account for the fact that it might be called passing in your generated types.