Skip to content

Discriminated Union code-generation

Compare
Choose a tag to compare
@louthy louthy released this 09 Nov 01:10

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.