F# 3.0 was released this week at the Microsoft Build conference as part of the Visual Studio 11 Developer Preview. Full details are available on the F# team blog – I recommend you read through this and have a play with the new features – it’s a really feature packed release.
I’m interested specifically in Type Providers, which are a new direction in F#, and provide some of the power of dynamic languages (types generated on the fly to wrap some form of structured data, such as XML from an OData service) but without the performance issues or late binding that come with these. Type Providers are actually an extensibility mechanism for the compiler, which allow for the compiler to query the type provider assembly, which does exactly that – provides types on the fly, which both the compiler and VS Intellisense can make use of. This all happens at compile time, and therefore whilst the code has features with a dynamic flavour, the result remains statically typed.
What I want to do is to create the simplest possible example of a custom type provider. Essentially I would like to create one which provides only one type, and that itself is just a static type within the type provider assembly, no Emit code this time around.
FSharp.Data.TypeProviders
There doesn’t appear to be much (any?) information available yet concerning custom type providers, so my first port of call is to crack open the key F# type providers assembly – FSharp.Data.TypeProviders.dll using Reflector, and see what I can find.
The key touch point appears to be in Microsoft.FSharp.Data.TypeProviders.DesignTime.DataProviders -
[Serializable, TypeProvider, CompilationMapping(SourceConstructFlags.ObjectType)]
public class DataProviders : ITypeProvider, IProvidedNamespace, IDisposable
{
// ...
}
And the key interface here, defined in Microsoft.FSharp.Core.CompilerServices is:
public interface ITypeProvider : IDisposable
{
// Events
[CLIEvent]
event EventHandler Invalidate;
// Methods
Type ApplyStaticArguments(Type typeWithoutArguments, string typeNameWithArguments, object[] staticArguments);
Expression GetInvokerExpression(MethodBase syntheticMethodBase, ParameterExpression[] parameters);
IProvidedNamespace[] GetNamespaces();
ParameterInfo[] GetStaticParameters(Type typeWithoutArguments);
}
So I should be able to create an assembly (in C# to start off with) containing a class of my own which implements this above interface, and that should give me a type provider, right?
using System;
using System.Reflection;
using Microsoft.FSharp.Core;
using Microsoft.FSharp.Core.CompilerServices;
[Serializable, TypeProvider, CompilationMapping(SourceConstructFlags.ObjectType)]
public class BasicTypeProvider : ITypeProvider, IProvidedNamespace, IDisposable
{
Type baseType;
public BasicTypeProvider()
{
baseType = typeof(StaticType);
}
public Type ApplyStaticArguments(Type typeWithoutArguments, string typeNameWithArguments, object[] staticArguments)
{
return baseType;
}
public System.Linq.Expressions.Expression GetInvokerExpression(System.Reflection.MethodBase syntheticMethodBase, System.Linq.Expressions.ParameterExpression[] parameters)
{
return null;
}
public IProvidedNamespace[] GetNamespaces()
{
return new IProvidedNamespace[] { (IProvidedNamespace) this };
}
public System.Reflection.ParameterInfo[] GetStaticParameters(Type typeWithoutArguments)
{
return new ParameterInfo[] { };
}
public event EventHandler Invalidate;
public void Dispose()
{
}
public IProvidedNamespace[] GetNestedNamespaces()
{
return new IProvidedNamespace[] { };
}
public Type[] GetTypes()
{
return new Type[] { baseType };
}
public string NamespaceName
{
get { return "RegistryTypeProvider"; }
}
public Type ResolveTypeName(string typeName)
{
if (string.Equals(typeName, "StaticType"))
{
return this.baseType;
}
return null;
}
}
Note that I have included the same attributes as used in the FSharp.Data.TypeProviders assembly, as I assume they’re important, and in all the implementations I just shunt around the StaticType type (which is just a POCO class with a couple of properties, for demo purposes). I don’t know how to handle GetInvokerExpression at the moment, so I’ll return null here, and just hope I get away with it.
Now I build this assembly, and then create a new F# console application project to act as the consumer of this type provider. Add a reference to the type provider assembly and then create a simple use in Program.fs -
open TestTypeProvider [<Generate>] type myType = TestTypeProvider.BasicTypeProvider
Sadly this doesn’t work. I get red squigglies under TestTypeProvider.BasicTypeProvider and the message “This is not a provided type. Consider removing the 'Generate' attribute from this type definition.” Thanks. Actually I’d prefer to have a working type provider, if you don’t mind.
If I look back at FSharp.Data.TypeProviders.dll I can see an assembly level attribute which might be important:
... [assembly: TypeProviderAssembly] ...
So I add that to my type provider project, rebuild (and looks like I need to remove the type provider assembly reference from my consumer project, and then restart visual studio in order to avoid problems with the type provider assembly being locked).
Nope. “This is not a provided type. Consider removing the 'Generate' attribute from this type definition.”
I’ve now tried many tweaks to the attributes and restarted VS many times but no luck getting around this error message. I think I will take a look at this again in the morning, and dig around inside Reflector to see what else I can discover.
6 comments:
Chris,
Managed to reach the same point (not compiling from F#). Wondering if we're missing an attribute somewhere...
Phil
module Dummy =
[]
do ()
[]
[]
type Bang () =
let invalidate = Event<_,_>()
interface Microsoft.FSharp.Core.CompilerServices.IProvidedNamespace with
member x.GetTypes() = [||]
member x.NamespaceName = "Bang"
member x.ResolveTypeName (_) = failwith ""
member x.GetNestedNamespaces () = failwith ""
interface Microsoft.FSharp.Core.CompilerServices.ITypeProvider with
member x.ApplyStaticArguments (_,_,_) = failwith ""
member x.GetInvokerExpression (_,_) = failwith ""
member x.GetNamespaces() = failwith ""
member x.GetStaticParameters(_) = failwith ""
[]
member x.Invalidate = invalidate.Publish
interface System.IDisposable with
member x.Dispose() = ()
[]
type B = Bang
Now with the attributes visible:
module Dummy =
[< Microsoft.FSharp.Core.CompilerServices.TypeProviderAssembly >]
do ()
[< Microsoft.FSharp.Core.CompilerServices.TypeProvider >]
[< Microsoft.FSharp.Core.CompilerServices.TypeProviderEditorHideMethods >]
type Bang () =
let invalidate = Event<_,_>()
interface Microsoft.FSharp.Core.CompilerServices.IProvidedNamespace with
member x.GetTypes() = [||]
member x.NamespaceName = "Bang"
member x.ResolveTypeName (_) = failwith ""
member x.GetNestedNamespaces () = failwith ""
interface Microsoft.FSharp.Core.CompilerServices.ITypeProvider with
member x.ApplyStaticArguments (_,_,_) = failwith ""
member x.GetInvokerExpression (_,_) = failwith ""
member x.GetNamespaces() = failwith ""
member x.GetStaticParameters(_) = failwith ""
[< CLIEvent >]
member x.Invalidate = invalidate.Publish
interface System.IDisposable with
member x.Dispose() = ()
[< Generate >]
type K = Bang
I was about to reimplement in F# to see if that somehow made a difference :)
I can't for the life of me find any other attributes, have tried different target frameworks, target CPUs etc...
Don - help!
Changing configuration solved the lock. I am facing problems further along, but once I finish I'll come back and see if I can find your problem.
First thing you missed - the requires constructor is not empty, but takes TypeProviderConfig as an argument.
Ramon - thanks, not sure how I missed that one. I have added now, but sadly makes no difference.
Post a Comment