Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using F# generative type provider types as generic type parameters

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?

like image 257
Wallace Kelly Avatar asked Feb 28 '14 03:02

Wallace Kelly


People also ask

What is f {} in Python?

“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.

What is the use of \F?

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.

What is %d and %f in Python?

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.

What is an F-string?

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'


1 Answers

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.

like image 67
Wallace Kelly Avatar answered Oct 29 '22 08:10

Wallace Kelly