Skip to content

Instantly share code, notes, and snippets.

@vzarytovskii
Last active December 12, 2022 13:58
Show Gist options
  • Save vzarytovskii/19f14182fddb3c04861431fb4183ea1c to your computer and use it in GitHub Desktop.
Save vzarytovskii/19f14182fddb3c04861431fb4183ea1c to your computer and use it in GitHub Desktop.
F# Compiler Notes

Discriminated unions notes

General

Reference DUs

Notes

Reference DUs constructors (cases) are represented as nested inherited classes.

Codegen

Below, there are some examples of F# compiler codegen for reference DUs.

type DU = D | U

roughly, the following code is generated (C# for simplicity):

public sealed class DU
{
    public static class Tags
    {
        public const int D = 0;
        public const int U = 1;
    }

    public int Tag { get; private set; }
    public static DU D { get; } = new DU(0);
    public static DU U { get; } = new DU(0);

    public bool IsD {
        get {
            return Tag == 0;
        }
    }

    public bool IsU {
        get {
            return Tag == 1;
        }
    }

    internal DU(int tag)
    {
        this.Tag = tag;
    }

}

Discriminated union with a single constructor:

will result in

public sealed class DU
{
    public int Tag { get; } = 0;
    public decimal Item { get; private set; }

    public static DU NewD(decimal item)
    {
        return new DU(item);
    }

    internal DU(decimal item)
    {
        this.Item = item;
    }
}

Discriminated unions with multiple constructors:

type DU = D of decimal | U of uint32

will result in

public abstract class DU
{
    public static class Tags
    {
        public const int D = 0;
        public const int U = 1;
    }

    public class D : DU
    {
        public decimal Item { get; private set;}
        internal D(decimal item)
        {
            this.Item = item;
        }
    }

    public class U : DU
    {
        public uint Item { get; private set;}
        internal U(uint item)
        {
            this.Item = item;
        }
    }

    public int Tag
    {
        get
        {
            return (this is U) ? 1 : 0;
        }
    }

    public bool IsD
    {
        get
        {
            return this is D;
        }
    }

    public bool IsU
    {
        get
        {
            return this is U;
        }
    }

    internal DU()
    {
    }

    public static DU NewD(decimal item)
    {
        return new D(item);
    }

    public static DU NewU(uint item)
    {
        return new U(item);
    }

}

Struct DUs

General

Major use-cases for the struct discriminated unions are lightweight wrappers for other struct-types, to achieve additional type safety, for example:

[<Struct>] type UserId = UserId of int

// And then it can be used in the following function:

let getUserById (UserId id) = () // Unable to pass a normal int here, since type won't match.

Notes

Struct DUs have stricter requirements than the reference DUs:

  • No callable default constructor

  • No cyclic references / no recursive definitions

  • Multi-case must have unique names

    • In the case of reference DUs, there's an inner/nested class for each case/constructor.
    • In the case of struct DUs, values are laid out flat in the struct, for the sake of performance/size (see example below)
  • The resulting struct won't be "packed" (not yet, see suggestion), i.e.

    [<Struct>]
      type MyType =
          | Case1 of v1:int
          | Case2 of v2:int
          | Case3 of v3:int

    will be internally represented in a struct with 3 different integers for values.

Codegen

[<Struct>]
type DU =
    | D of dval: decimal
    | U of uval: uint32

will result in

public struct DU
{
    public static class Tags
    {
        public const int D = 0;
        public const int U = 1;
    }


    public int Tag {get; private set; }

    public bool IsD
    {
        get
        {
            return Tag == 0;
        }
    }

    public bool IsU
    {
        get
        {
            return Tag == 1;
        }
    }
    public decimal dval { get; private set; }
    public uint uval { get; private set; }

    public static DU NewD(decimal _dval) => new DU(_dval, 0, false);
    public static DU NewU(uint _uval) => new DU(_uval, 1, 0);

    internal DU(decimal _dval, int _tag, bool P_2)
    {
        this.dval = _dval;
        this.Tag = _tag;
    }

    internal DU(uint _uval, int _tag, byte P_2)
    {
        this.uval = _uval;
        this.Tag = _tag;
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment