Trustbit

View Original

Using Discriminated Union Labelled Fields

Introduction

This is my entry to F# Advent Calendar 2021 Thanks to Sergey Tihon for organising the Advent Calendar each year.

A few weeks ago, I re-discovered labelled fields in discriminated unions:

See this content in the original post

I knew that the feature existed but I've usually built specific types, generally records, for each union case, so hadn't really used them in anger before. This isn't a new feature: Field labels in discriminated union case members were introduced in F# 3.1.

Despite the fact that they look like tuples, they are not. For example, tuples in F# do not support labels like they do in C#.

In this post, we will look at how to make use of this feature.

Getting Started

We are going to start with a simple business feature:

See this content in the original post

We are going to create two functions: One to calculate the totals after discount and one to return the email address of eligible customers. Emails are mandatory for Eligible customers and optional for Registered customers.

Creating Labelled Fields

The type design used in this post is specifically designed for the task of discovering how we can work with labelled fields. We start with a simple discriminated union with two union cases:

See this content in the original post

Pattern Matching on Labelled Fields

As they look like tuples, can we deconstruct them in a match expression in the same way without the labels? It turns out that you can:

See this content in the original post

Now let's try adding the labels in and get the values like we would with fields on a record type. Sadly, this doesn't work as we get a compiler error:

See this content in the original post

As I said earlier, they look like tuples but they aren't. The fix turns out to be simple: Replace the comma separators with semi-colons:

See this content in the original post

As we are not using name and email, can we use wildcards to ignore their data? Yes we can:

See this content in the original post

How about wildcards to ignore the fields? This change gives us a compiler error:

See this content in the original post

Again, the fix turns out to be simple: Remove the fields completely from the pattern match:

See this content in the original post

That's better but it would be nice if we could apply a filter directly rather than having to get the value and then test it. We can do this with records and thankfully it is available here too:

See this content in the original post

We can also combine the filter and the value getter as shown in the following function where we filter on IsEligible and return the value of the Email field into a local binding:

See this content in the original post

In summary, we use ',' for separating the fields when we don't use the labels in the pattern match and ';' when we do. If we are not interested in a field, don't use it in the match. We can use filters and value getters in the same match.

Creating an Instance of a DU Case

You can create an instance of a union case without specifying the field labels:

See this content in the original post

Personally, I think it makes more sense to use the labels if you provided them in the first place:

See this content in the original post

Verifying these the functions with the instances is trivial. Firstly, the calculateOrderTotal function:

See this content in the original post

and then the tryGetEligibleEmail function:

See this content in the original post

What Happens If ...

What happens if you decide not to include a label for the Name field?

See this content in the original post

The tuple-style pattern match with no labels works fine as does the version with the wildcards:

See this content in the original post

Removing the label from the original version with labels causes a compiler error:

See this content in the original post

Removing that field and only using labelled fields works correctly:

See this content in the original post

You don't have to supply every field with a label if you aren't going to pattern match on it with that label. I like consistency and would either supply labels to all fields or none at all.

Summary

I hope that you found this short post useful. Even if you decide not to use these features, it is still nice to know that they are available to you.

I have written an ebook called Essential Functional-First F#. All of the royalties go to the F# Software Foundation to support their promotion of the F# language and community around the world.

Follow me on Twitter at @ijrussell