Monday, May 17, 2010

An interesting bit of F# behaviour

In one assembly, define a public static class in C#:

namespace ClassLibrary1
{
public static class Class1
{
public static string Hello()
{
return "Hello";
}
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

In another do something similar in F#:

namespace ClassLibrary1
module Module1 =
let Hello () = "Hello"
view raw gistfile1.fs hosted with ❤ by GitHub

which Reflector tells us is equivalent to

[CompilationMapping(SourceConstructFlags.Module)]
public static class Module1
{
// Methods
public static string Hello()
{
return "Hello";
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

inside namespace ClassLibrary1. Now create another F# library referencing the previous two containing

namespace ClassLibrary1
module Module1 =
let type1 = typeof<ClassLibrary1.Class1>
let type2 = typeof<ClassLibrary1.Module1>
view raw gistfile1.fs hosted with ❤ by GitHub

The first line compiles; the second doesn't, failing with compile error error FS0039: The type 'Module1' is not defined.

Clearly the CompilationMapping is being sniffed by the typeof operation, because that is the only difference between the two.

4 comments :

Brian said...

It's not CompilationMapping, rather it's because typeof<AModule> is not allowed in F#. There's no good technical reason really, and we have a bug logged to remedy this, but it's a low priority since it's uncommon to want this for any real scenario.

Steve Gilham said...

The question is how it detected, in the separate assembly, that the code was an F# module rather than a C# static class -- and at the IL level the CompilationMapping attribute is the most blatant difference that can be what says "this static class is an F# module".

The use case is a simple piece of reflection -- wanting from one assembly to get the MethodInfo for one of the functions in the module, the sort of thing that in C# would just be typeof(Module1).GetMethod("Hello")

Brian said...

F# assemblies contain their own metadata (stored as a resource, see e.g. FSharp.PowerPack.Metadata), and it is the 'extra F# metadata' that identifies a static class as an F# module.

Steve Gilham said...

Thatks for the detail -- I stand corrected on the precise detection mechanism.

The point however remains that tyepof<'T> is examining F# fingerprints (not just the particulargaudy red herring I first spotted) and filtering accordingly. This isn't the obvious behaviour cross-assembly (where the source language isn't an obvious part of the public interface), so it's still worth flagging.