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?
“F-strings provide a way to embed expressions inside string literals, using a minimal syntax. It should be noted that an f-string is really an expression evaluated at run time, not a constant value. In Python source code, an f-string is a literal string, prefixed with f , which contains expressions inside braces.
The most common one is newline \n , but there are others. Many of them became less and less useful over time, such as \f . This is called form feed, is used to indicate to a printer that it should start a new page.
For example, "print %d" % (3.78) # This would output 3 num1 = 5 num2 = 10 "%d + %d is equal to %d" % (num1, num2, num1 + num2) # This would output # 5 + 10 is equal to 15. The %f formatter is used to input float values, or numbers with values after the decimal place.
To define an f-string, you simply place an f before your string's first quotation mark, and insert variables wherever you want in the string using {} . Let's take a look at an example. Below, we define the same string as above, but this time using an f-string: >>> fruit = 'persimmon'
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With