Saturday, November 21, 2009

F# under the covers X -- the curious case of record types

This is unchanged in the February 2010 CTP (1.9.9.9) release.

They're coming thick and fast now...

Define a record type such as

type internal Context = {
Line : int;
Column : int;
EndLine : int;
EndColumn : int }
view raw gistfile1.fs hosted with ❤ by GitHub

The class that results looks like

[Serializable, CompilationMapping(SourceConstructFlags.RecordType)]
public sealed class Context : IStructuralEquatable, IComparable, IStructuralComparable
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal int Column@;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal int EndColumn@;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal int EndLine@;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal int Line@;
// Methods
public Context(int line, int column, int endLine, int endColumn);
[CompilerGenerated]
public sealed override int CompareTo(object obj);
[CompilerGenerated]
public int CompareTo(Context obj);
[CompilerGenerated]
public sealed override int CompareTo(object obj, IComparer comp);
[CompilerGenerated]
public sealed override bool Equals(object obj);
[CompilerGenerated]
public bool Equals(Context obj);
[CompilerGenerated]
public sealed override bool Equals(object obj, IEqualityComparer comp);
[CompilerGenerated]
public sealed override int GetHashCode();
[CompilerGenerated]
public sealed override int GetHashCode(IEqualityComparer comp);
// Properties
[CompilationMapping(SourceConstructFlags.Field, 1)]
public int Column { get; }
[CompilationMapping(SourceConstructFlags.Field, 3)]
public int EndColumn { get; }
[CompilationMapping(SourceConstructFlags.Field, 2)]
public int EndLine { get; }
[CompilationMapping(SourceConstructFlags.Field, 0)]
public int Line { get; }
}
view raw gistfile1.cs hosted with ❤ by GitHub

where the source context for each method is the same -- the range containing just the type name.

Now, you wouldn't expect anything to be seriously unusual about this class in terms of its implementation, but there is.

FxCop reminds you about the IComparable should-haves that can't be enforced through interface constraints:

[Location not stored in Pdb] : warning : CA1036 : Microsoft.Design : 'Context' should define operator '!=' since it implements IComparable.
[Location not stored in Pdb] : warning : CA1036 : Microsoft.Design : 'Context' should define operator '<' since it implements IComparable.
[Location not stored in Pdb] : warning : CA1036 : Microsoft.Design : 'Context' should define operator '==' since it implements IComparable.
[Location not stored in Pdb] : warning : CA1036 : Microsoft.Design : 'Context' should define operator '>' since it implements IComparable.

but which aren't there; and Reflector's decompilation to C# is stymied by the highlighted CompareTo overloads -- the simple one is implemented as

[CompilerGenerated]
public sealed override int CompareTo(object obj)
{
return this.CompareTo((Context) obj);
}
view raw gistfile1.cs hosted with ❤ by GitHub

which fortunately doesn't give much scope for things to go wrong.

In this and the previous case, the offending instruction that Reflector balks at is a simple branch such as indicated in this example

L_0045: ldc.i4.m1
L_0046: nop
L_0047: br.s L_0050
L_0049: ldloc.s num2
L_004b: ldloc.s num3
L_004d: cgt
L_004f: nop
L_0050: stloc.2
view raw gistfile1.cs hosted with ❤ by GitHub

It's quite clear at every turn that F# is coming at the problem of code generation from a very different direction to the well explored parts of the phase space of valid IL that C# and VB.net dabble in. And clear, too, that this will present a significant challenge to all writers of tools to work with the language -- it takes us well out of our old comfort zone.

No comments :