I have an idea for a type provider, so now that the type provider bits are finally publicly available I set to work building it. However it turns out just implementing a type provider is pretty tricky (not really that much of a surprise I supose), so I thought it was worth a quick blog post to run through the basics.
A type provider is a class that implements the interface ITypeProvider (fullname Microsoft.FSharp.Core.CompilerServices.ITypeProder), which lives in the assembly FSharp.Core.dll. Its definition is as follows:
public interface ITypeProvider : IDisposable
{
event EventHandler Invalidate;
IProvidedNamespace[] GetNamespaces();
ParameterInfo[] GetStaticParameters(Type typeWithoutArguments);
Type ApplyStaticArguments(Type typeWithoutArguments, string typeNameWithArguments, object[] staticArguments);
Expression GetInvokerExpression(MethodBase syntheticMethodBase, ParameterExpression[] parameters);
}
(Sorry for the C#, it’s from a decompiled definition)
To implement this interface you must also implement the IProvidedNamespace interface:
public interface IProvidedNamespace
{
string NamespaceName
{
get;
}
IProvidedNamespace[] GetNestedNamespaces();
Type[] GetTypes();
Type ResolveTypeName(string typeName);
}
Once implemented you need to add the TypeProviderAssembly attribute to your assembly, plus you need to mark your type provider with the atteibute TypeProvider. So a basic implementation would look something like:
[<assembly: TypeProviderAssembly>]
do()
type TypeProvidedNamespace(name, assembly) =
do trace()
interface IProvidedNamespace with
member x.NamespaceName
with get() =
trace()
name
member x.GetNestedNamespaces() =
trace()
[||]
member x.GetTypes() =
trace()
[|new ProvidedType("Atype", name, assembly, IsErased = true)|]member x.ResolveTypeName(typeName: string) =
trace()
null
[<TypeProvider>]
type TypeProviderRoot() =
do trace()
let theAssembly = typeof<TypeProviderRoot>.Assembly
let invalidate = new Event<EventHandler,EventArgs>()
interface ITypeProvider with
[<CLIEvent>]
member x.Invalidate = invalidate.Publish
member x.GetNamespaces() =
trace()
[|new TypeProvidedNamespace("ExampleTypeProvider", theAssembly)|]
member x.GetStaticParameters(typeWithoutArguments: Type) =
trace()
[||]
member x.ApplyStaticArguments(typeWithoutArguments: Type, typeNameWithArguments: string, staticArguments: obj[]) =
trace()
null: Type
member x.GetInvokerExpression(syntheticMethodBase: MethodBase, parameters: ParameterExpression[]) =
trace()
null: Expression
member x.Dispose() =
trace()
()
Not so tricky so far. When you reference and try to use you type provider the F# compiler creates a dynamic instance of the type provider and then calls GetNamespaces on the type provider and then on each returned namespace, each an implementation of IProvidedNamespace, it calls GetTypes(). This is the tricky bit, GetTypes returns an array of System.Types, so you need your own implemenation of System.Type. System.Type is an abstract type so there’s technically no difficultly in deriving from it, but it has a lot of abstract members, so there a lot of abstract members to implement. Worse still for some strange reason System.Type also has a couple of virtual methods that simply throw NotImplementedException as there implementation. One such method is GetCustomAttributesData, and this used by the F# compiler so it’s important that you implement it. It’s also important to take care when implementing GetAttributeFlagsImpl. This returns an TypeAttributes enum that tells the compiler various information about the type, such as whether it is public etc. Type provider adds an extra enumeration member IsErased, that tells the F# compiler whether the type provider generates types into the client assembly or whether the types generated are fully dynamic.
Anyway, once you’ve implmented System.Type you can simply return an instance from the GetTypes call and you’ve created your first provided type. Of course, for it to do anything interesting you need to also implement System.Reflection.MethodInfo, System.Reflection.PropertyInfo, and probably System.Reflection.ConstructorInfo, but I haven’t done this yet. So just about the only valid program you can write that uses the provided type is:
#r "bin\Debug\ExampleTypeProvider.dll";;
type t = ExampleTypeProvider.Atype
A word about testing
One annoying aspect of creating a type provider is you can’t really test it from within visual studio. As soon as VS opens a script or project with a reference to the type provider it takes a lock on the assembly containing the provider. This means the project containing the provider will no longer be able to compile as it won’t be able to overwrite the assembly in bin\debug. To get round this I created a simple script that used my type provider and executed it using fsi.
Well that about wraps it up for now. It looks like type providers will open up lots of fun options, but for the moment they take quite a bit of implementing. I’m thinking of taking my work so far and wrapping it up into a tool kit for implementing type providers, but we’ll have to see how that pans out. For now you can see the type provider on in my misc. F# example on github.