Sunday, September 06, 2009

F# algebraic types under the covers

Consider the simple type

// After the Haskell type
type internal Either<'a, 'b> =
| Left of 'a
| Right of 'b
view raw gistfile1.fs hosted with ❤ by GitHub

Innocuous, right? Well look at what Reflector has to say about the May 2009 CTP version...

[Serializable, DebuggerDisplay("{__DebugDisplay()}"), CompilationMapping(SourceConstructFlags.NonpublicRepresentation | SourceConstructFlags.SumType)]
internal abstract class Either<a, b> : IStructuralEquatable, IComparable, IStructuralComparable
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
internal const int tag_Left = 0;
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
internal const int tag_Right = 1;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
internal protected Either();
internal object __DebugDisplay();
[CompilerGenerated]
internal int CompareTo(Either<a, b> obj);
[CompilerGenerated]
public sealed override int CompareTo(object obj);
[CompilerGenerated]
public sealed override int CompareTo(object obj, IComparer comp);
[CompilerGenerated]
public sealed override bool Equals(object obj);
[CompilerGenerated]
internal bool Equals(Either<a, b> obj);
[CompilerGenerated]
public sealed override bool Equals(object obj, IEqualityComparer comp);
[CompilerGenerated, DebuggerNonUserCode]
public override int GetHashCode();
[CompilerGenerated]
public sealed override int GetHashCode(IEqualityComparer comp);
[CompilerGenerated, DebuggerNonUserCode]
internal bool IsLeft();
[CompilerGenerated, DebuggerNonUserCode]
internal bool IsRight();
[CompilationMapping(SourceConstructFlags.UnionCase, 0)]
internal static Either<a, b> Left(a Left1);
[CompilationMapping(SourceConstructFlags.UnionCase, 1)]
internal static Either<a, b> Right(b Right1);
// Properties
[CompilationMapping(SourceConstructFlags.Field, 0, 0), CompilerGenerated, DebuggerNonUserCode]
internal a Left1 { [CompilerGenerated, DebuggerNonUserCode] get; }
[CompilationMapping(SourceConstructFlags.Field, 1, 0), CompilerGenerated, DebuggerNonUserCode]
internal b Right1 { [CompilerGenerated, DebuggerNonUserCode] get; }
[CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal int Tag { [CompilerGenerated, DebuggerNonUserCode] get; }
// Nested Types
[Serializable, DebuggerTypeProxy(typeof(Either<a, b>._Left@DebugTypeProxy<,>)), DebuggerDisplay("{__DebugDisplay()}")]
internal class _Left : Either<a, b>
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
internal readonly a left1;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
internal _Left(a left1);
}
private class _Left@DebugTypeProxy
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
private Either<a, b>._Left _obj;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
public _Left@DebugTypeProxy(Either<a, b>._Left obj);
// Properties
[CompilationMapping(SourceConstructFlags.Field, 0, 0), CompilerGenerated, DebuggerNonUserCode]
public a Left1 { [CompilerGenerated, DebuggerNonUserCode] get; }
}
[Serializable, DebuggerTypeProxy(typeof(Either<a, b>._Right@DebugTypeProxy<,>)), DebuggerDisplay("{__DebugDisplay()}")]
internal class _Right : Either<a, b>
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
internal readonly b right1;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
internal _Right(b right1);
}
private class _Right@DebugTypeProxy
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
private Either<a, b>._Right _obj;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
public _Right@DebugTypeProxy(Either<a, b>._Right obj);
// Properties
[CompilationMapping(SourceConstructFlags.Field, 1, 0), CompilerGenerated, DebuggerNonUserCode]
public b Right1 { [CompilerGenerated, DebuggerNonUserCode] get; }
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

Most of this is tagged as [CompilerGenerated] -- with some surprising omissions, like

internal object __DebugDisplay();
[CompilationMapping(SourceConstructFlags.UnionCase, 0)]
internal static Either<a, b> Left(a Left1);
[CompilationMapping(SourceConstructFlags.UnionCase, 1)]
internal static Either<a, b> Right(b Right1);
view raw gistfile1.cs hosted with ❤ by GitHub

which makes filtering out what is written vs what is generated for coverage analysis purposes rather more tedious than it might be. Arguably, in a unit test using the type, the latter two should get exercised anyway, but having the __DebugDisplay() method left over seems like a bit of an oversight.


By way of comparison, the Feb 2010 CTP yields

[Serializable, DebuggerDisplay("{__DebugDisplay(),nq}"), CompilationMapping(SourceConstructFlags.NonPublicRepresentation | SourceConstructFlags.SumType)]
internal abstract class Either<a, b> : IEquatable<Either<a, b>>, IStructuralEquatable, IComparable<Either<a, b>>, IComparable, IStructuralComparable
{
// Methods
[CompilerGenerated, DebuggerNonUserCode]
internal Either();
[CompilerGenerated, DebuggerNonUserCode]
internal object __DebugDisplay();
[CompilerGenerated]
public sealed override int CompareTo(object obj);
[CompilerGenerated]
public sealed override int CompareTo(Either<a, b> obj);
[CompilerGenerated]
public sealed override int CompareTo(object obj, IComparer comp);
[CompilerGenerated]
public sealed override bool Equals(object obj);
[CompilerGenerated]
public sealed override bool Equals(Either<a, b> 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);
[CompilationMapping(SourceConstructFlags.UnionCase, 0)]
internal static Either<a, b> NewLeft(a item);
[CompilationMapping(SourceConstructFlags.UnionCase, 1)]
internal static Either<a, b> NewRight(b item);
// Properties
[CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal bool IsLeft { [CompilerGenerated, DebuggerNonUserCode] get; }
[CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal bool IsRight { [CompilerGenerated, DebuggerNonUserCode] get; }
[CompilerGenerated, DebuggerNonUserCode, DebuggerBrowsable(DebuggerBrowsableState.Never)]
internal int Tag { [CompilerGenerated, DebuggerNonUserCode] get; }
// Nested Types
[Serializable, DebuggerTypeProxy(typeof(Either<a, b>.Left@DebugTypeProxy<,>)), DebuggerDisplay("{__DebugDisplay(),nq}")]
internal class Left : Either<a, b>
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
internal readonly a item;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
internal Left(a item);
// Properties
[CompilationMapping(SourceConstructFlags.Field, 0, 0), CompilerGenerated, DebuggerNonUserCode]
internal a Item { [CompilerGenerated, DebuggerNonUserCode] get; }
}
internal class Left@DebugTypeProxy
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
internal Either<a, b>.Left _obj;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
public Left@DebugTypeProxy(Either<a, b>.Left obj);
// Properties
[CompilationMapping(SourceConstructFlags.Field, 0, 0), CompilerGenerated, DebuggerNonUserCode]
public a Item { [CompilerGenerated, DebuggerNonUserCode] get; }
}
[Serializable, DebuggerTypeProxy(typeof(Either<a, b>.Right@DebugTypeProxy<,>)), DebuggerDisplay("{__DebugDisplay(),nq}")]
internal class Right : Either<a, b>
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
internal readonly b item;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
internal Right(b item);
// Properties
[CompilationMapping(SourceConstructFlags.Field, 1, 0), CompilerGenerated, DebuggerNonUserCode]
internal b Item { [CompilerGenerated, DebuggerNonUserCode] get; }
}
internal class Right@DebugTypeProxy
{
// Fields
[DebuggerBrowsable(DebuggerBrowsableState.Never), CompilerGenerated, DebuggerNonUserCode]
internal Either<a, b>.Right _obj;
// Methods
[CompilerGenerated, DebuggerNonUserCode]
public Right@DebugTypeProxy(Either<a, b>.Right obj);
// Properties
[CompilationMapping(SourceConstructFlags.Field, 1, 0), CompilerGenerated, DebuggerNonUserCode]
public b Item { [CompilerGenerated, DebuggerNonUserCode] get; }
}
internal static class Tags
{
// Fields
public const int Left = 0;
public const int Right = 1;
}
}
view raw gistfile1.cs hosted with ❤ by GitHub

which is broadly similar, but differs in detail (such as the Tags values and the DebuggerDisplay attributes -- and the bug about __DebugDisplay not having the CompilerGenerated attribute is fixed.

2 comments :

Anonymous said...

Feel free to send stuff like this to fsbugs@microsoft.com. I've gone ahead and filed a bug about __DebugDisplay().

Steve Gilham said...

Thanks! -- and I'll keep that address in mind for the next time I find a little quirk.