- Generative Type Provider
- Erasing Type Provider
The type providers in FSharp.Data.TypeProviders are generative. The generated types can easily be used from C#. A generative type provider calls another tool to generate the types, such as System.Reflection.Emit, System.CodeDom.Compile, or Roslyn.Compilers. Aside, where is our "Roslyn" kind of API for F#?! The type provider then gets the Common Intermediate Language bytes from the assembly and the F# compiler puts them in the output assembly. The generated types can be used from a C# project without referencing the library containing the type providers.
The type providers in FSharpx.TypeProviders are erasing. These types are only usable in F#. They are built in memory using F# Quotations. The F# compiler compiles them down to something else. The library containing the type provider is needed at compile time by any dependent assemblies. I'm not sure if it is needed at runtime too.I'm hoping it isn't. For implementing these types, there is a FSharpx.TypeProviders.DSL that may help. I'm curious what the benefits of erasing type providers are.
Debugging an F# Type Provider
The easiest way I found to debug the type provider was to add a System.Diagnostics.Debugger.Break() call somewhere in the type provider then run "fsc TestVectorTP.fsx" from the command prompt. You will get a message saying "Fsc.exe has stopped working". After a few seconds, you can select "Debug". Select the Visual Studio 2012 instance that has the type provider open and check "Manually choose the debugging engines." and click "Yes". You don't need to debug "Native" code, so deselect it and hit "OK". You can then add breakpoints as needed.
Generative Type Provider Example
The blog from Ivan Towlson on 2011-09-19 titled "F# type providers - as if by magic..." is an example of a generative type provider. It generates types using System.CodeDom.Compile. I updated the type provider to work with the final F# 3.0 bits. The result is VectorTP.fs. I put in printfn statements to figure out how a type provider works by showing which ITypeProvider and IProvidedNamespace methods are called and it what order. With this TestVectorTP.fsx script:
#r "bin\Debug\VectorTP.dll"
type Vector2D = Mindscape.Vectorama.Vector<"X", "Y">
let v = Vector2D()
v.X <- 100.0
v.Y <- 200.0
printfn "Vector X:%f , Y: %f" v.X v.Y
Compiling it with fsc.exe results in this output:
C:\Users\taggartc\Git\blog2012q3\VectorTP>fsc TestVectorTP.fsx
Microsoft (R) F# Compiler version 11.0.50727.1 Copyright (c) Microsoft Corporation. All Rights Reserved. ITypeProvider.Invalidate ITypeProvider.GetNamespaces() IProvidedNamespace.NamespaceName.get() IProvidedNamespace.GetTypes() IProvidedNamespace.GetNestedNamespaces() ITypeProvider.GetStaticParameters(VectorTP.Vector) ITypeProvider.ApplyStaticArguments(VectorTP.Vector, [|"TestVectorTP"; "Vector2D"|], [|"X"; "Y"; ""; ""; ""; ""; ""|]) ITypeProvider.GetGeneratedAssemblyContents(tmp55CC, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null) ReadAllBytes C:\Users\taggartc\AppData\Local\Temp\tmp55CC.tmp ITypeProvider.GetInvokerExpression(Void .ctor(), [||]) ITypeProvider.GetInvokerExpression(Void set_X(Double), [|this; arg0|]) ITypeProvider.GetInvokerExpression(Void set_Y(Double), [|this; arg0|]) ITypeProvider.GetInvokerExpression(Double get_X(), [|this|]) ITypeProvider.GetInvokerExpression(Double get_Y(), [|this|])
I created an F# library that uses the type provider library to create a type:
namespace VectorTypes
type Vector2D = Mindscape.Vectorama.Vector<"X", "Y">
I then created a C# Console application that uses that library:
using System;
namespace VectorTypesFromCSharp
{
class Program
{
static void Main(string[] args)
{
var v = new VectorTypes.Vector2D();
v.X = 100;
v.Y = 200;
Console.WriteLine("X: {0}, Y: {1}", v.X, v.Y);
Console.ReadKey(true);
}
}
}