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 |
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; } | |
} | |
} |
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); |
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; | |
} | |
} |
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 :
Feel free to send stuff like this to fsbugs@microsoft.com. I've gone ahead and filed a bug about __DebugDisplay().
Thanks! -- and I'll keep that address in mind for the next time I find a little quirk.
Post a Comment