Discriminated Union code-generation
In this release the code-generation story has been extended to support sum-types (also known as 'discriminated unions', 'union types', or 'case types').
Simply declare an interface
with the attribute [Union]
where all methods declared in the interface return the type of the interface, i.e.
[Union]
public interface Maybe<A>
{
Maybe<A> Just(A value);
Maybe<A> Nothing();
}
It has similar behaviour to this, in F#:
type Maybe<'a> =
| Just of 'a
| Nothing
In the above example, two case-types classes will be created Just<A>
and Nothing<A>
as well as static
constructor class called Maybe
:
var maybe = Maybe.Just(123);
var res = maybe switch
{
Just<int> just => just.Value,
Nothing<int> _ => 0
};
This is the generated code:
public partial class Just<A> : LanguageExt.Record<Just<A>>, Maybe<A>
{
public readonly A Value;
Maybe<A> Maybe<A>.Just(A value) => throw new System.NotSupportedException();
Maybe<A> Maybe<A>.Nothing() => throw new System.NotSupportedException();
public Just(A value)
{
Value = value;
}
}
public partial class Nothing<A> : LanguageExt.Record<Nothing<A>>, Maybe<A>
{
Maybe<A> Maybe<A>.Just(A value) => throw new System.NotSupportedException();
Maybe<A> Maybe<A>.Nothing() => throw new System.NotSupportedException();
public Nothing()
{
}
}
public static partial class Maybe
{
public static Maybe<A> Just<A>(A value) => new Just<A>(value);
public static Maybe<A> Nothing<A>() => new Nothing<A>();
}
The generated code is relatively basic at the moment. It will be extended to support abstract class
types and will auto-generate structural equality behaviour as well as other useful behaviours. But for now this is a super-quick way to generate the cases for a union-type and have a simple way of constructing them.
The generated types are all partial
and can therefore be extended trivially.
Here's another simple example:
[Union]
public interface Shape
{
Shape Rectangle(float width, float length);
Shape Circle(float radius);
Shape Prism(float width, float height);
}
And the generated code:
public partial class Rectangle : LanguageExt.Record<Rectangle>, Shape
{
public readonly float Width;
public readonly float Length;
Shape Shape.Rectangle(float width, float length) => throw new System.NotSupportedException();
Shape Shape.Circle(float radius) => throw new System.NotSupportedException();
Shape Shape.Prism(float width, float height) => throw new System.NotSupportedException();
public Rectangle(float width, float length)
{
Width = width;
Length = length;
}
}
public partial class Circle : LanguageExt.Record<Circle>, Shape
{
public readonly float Radius;
Shape Shape.Rectangle(float width, float length) => throw new System.NotSupportedException();
Shape Shape.Circle(float radius) => throw new System.NotSupportedException();
Shape Shape.Prism(float width, float height) => throw new System.NotSupportedException();
public Circle(float radius)
{
Radius = radius;
}
}
public partial class Prism : LanguageExt.Record<Prism>, Shape
{
public readonly float Width;
public readonly float Height;
Shape Shape.Rectangle(float width, float length) => throw new System.NotSupportedException();
Shape Shape.Circle(float radius) => throw new System.NotSupportedException();
Shape Shape.Prism(float width, float height) => throw new System.NotSupportedException();
public Prism(float width, float height)
{
Width = width;
Height = height;
}
}
public static partial class ShapeCon
{
public static Shape Rectangle(float width, float length) => new Rectangle(width, length);
public static Shape Circle(float radius) => new Circle(radius);
public static Shape Prism(float width, float height) => new Prism(width, height);
}
NOTE: The code-gen doesn't yet support .NET Core 3.0 - I'm still waiting for the Roslyn code-gen project to be updated. If it isn't forthcoming soon, I'll look for other options.