State of the Discriminated Union

C# C Sharp Programming Discriminated Union

Erik Svensson

Dotnet-utvecklare

Third party goodness for C#, as the state of the (native) discriminated union is still unclear.

You might have heard of the discriminated union type, as it’s called in F#. It’s a special type that’s available in a range of statically-typed multi-paradigm languages, including Rust, Swift, Scala and Typescript. But C# developers are still missing out.

Union type C#

Twitter Union type C#

 

Design discussions are still ongoing in dotnet/csharplang/issue#113, and it seems unlikely that it will make it into C# 11.


So what is it and why do people want it?

The discriminated union type simply states that data can be one of a range of pre-defined types. Like this:

type SaveCustomerResult =
   | CustomerCreated
   | CustomerUpdated
   | SaveFailed


CustomerSaveResult is compile-time guaranteed be of either type CustomerCreated, CustomerUpdated or SaveFailed. It can also be inferred from any of the union’s child types. Which allows you do write (pseudo) code like this:

var status = save(customerData)
   match
      | CustomerCreated => "customer was created"
      | CustomerUpdated => "customer was updated"
      | SaveFailed => "could not save customer"
SaveCustomerResult save(customer) {
   try {
       if (exists(customer.id)) {
           update(customer)
           return CustomerUpdated
       }
       create(customer)
       return CustomerCreated
   }
   catch {
       return SaveFailed
   }
}


If you’re doing something that has a range of outcomes, discriminated unions help you describe it simply and with type safety, leaving no stone unturned — discouraging logic based on null cases or polymorphism.


Harry McIntyre to the Rescue

Harry and a range of open-source contributors provide the OneOf library for dotnet. Which brings F# style d̶i̶s̶c̶r̶i̶m̶i̶n̶a̶t̶e̶d̶ unions for C#. The library has been available since 2016 and is ever-increasing in popularity. Harry is also an active voice in the design discussions for a potential C# native implementation.

 

On the d̶i̶s̶c̶r̶i̶m̶i̶n̶a̶t̶e̶d̶ part

For a union to be discriminated it must consist only of different types. But due to limitations of C#’s generic type constraints, OneOf<string, string> will compile. This type check limitation might be related to why we don’t have native discriminated unions in C# yet.

 

Let’s look at some OneOf code

First off, OneOf brings exhaustive pattern matching with Match and Switch. Meaning that you must explicitly declare all outcomes or else it won’t compile. Compare to the built-in switch which let’s you compile and forget that you have unmanaged cases.

OneOf<Yes, No, Maybe> union ... ;
// Use Switch when you don't want to return anything
union.Switch(
    yes => Console.WriteLine("yes"),
    no => Console.WriteLine("no"),
    maybe => Console.WriteLine("maybe"));
// Use Match to return a value
string response = union.Match(
   yes => "yes",
   no => "no",
   maybe => "maybe");


Using the pseudo code example above we can write it with C# and OneOf instead. For more and better examples checkout the GitHub repo of OneOf.

var status = Save(customerData)
                .Match(
                    created => "customer was created",
                    updated => "customer was updated",
                    failed => "could not save customer");
OneOf<CustomerCreated,
      CustomerUpdated,
      SaveFailed> Save(CustomerData customer)
{
   try
   {
       if (_repository.Exists(customer.id)) {
           _repository.Update(customer);
           return new CustomerUpdated();
       }
       _repository.Create(customer);
       return new CustomerCreated();
   }
   catch
   {
       return new SaveFailed();
   }
}


Other Types Derived from Discriminated Unions

While this post focused on the union type itself, there is a whole functional world of possibilities opening up just by having the option to say that something is one-of some other things. I’m thinking about certain monads that help remove if and else branching logic in your code, making it more readable and less error prone.

I will write a follow-up post on this, where I talk about my OneOf.Monads library.


Until next time!

Fler inspirerande inlägg du inte får missa