Trustbit

View Original

Alternate Ways of Creating Single Case Discriminated Unions in F#

Introduction

There are quite a few ways of creating single case discriminated unions in F# and this makes them popular for wrapping primatives. In this post, I will go through a number of the approaches that I have seen. The inspiration for this post is Twitter threads from the likes of @McCrews, @asp_net and @jordan_n_marr. It is in no way exhaustive but should give you plenty of ideas.

Basics

We start with a simple single case discriminated union:

See this content in the original post

To create a CustomerId and deconstruct it to get the value, we can do the following:

See this content in the original post

We can add a private modifier to it which means that we do not have access to the constructor and cannot create a CustomerId outside of the module the type is defined in:

See this content in the original post

This is easy to solve in a number of ways, firstly using a module.

Using a Module

If we create a module with the same name as the type definition, we can add functions to create and extract the value:

See this content in the original post

We can now create a CustomerId and extract the value from it like this:

See this content in the original post

It's also possible to do this without the need for a module.

Without Using a Module

We can add an instance member for extracting the value and a static member to create an instance of CustomerId:

See this content in the original post

This now makes extracting the value much cleaner:

See this content in the original post

If you think that the two lines for the Value function are one too many, we can write it in one line. This is the first version using the in keyword:

See this content in the original post

It doesn't impact how we create and extract the data:

See this content in the original post

Another way is to use an anonymous function:

See this content in the original post

Again the usage is the same as before:

See this content in the original post

Having a separate New function means that we could add logic to ensure that it cannot be created with an invalid value. If you don't need that, we can remove the private access modifier and the New function:

See this content in the original post

Usage then looks like this:

See this content in the original post

Alternate Solution

After I'd posted this article, @Savlambda suggested an alternate approach using an active pattern:

See this content in the original post

Using the new module looks like this:

See this content in the original post

So we have quite a few styles and I'm not going to suggest the superiority of one over the others.

In one of the Twitter threads that led to this post, @pblasucci said that you can do the same with records as well!

Using a Record Type

Let's create a simple record type:

See this content in the original post

Creation and value extraction look like this:

See this content in the original post

That's not too different! We can even add member functions to record types:

See this content in the original post

This means that we can create and extract the value in exactly the same way that we did with single case discriminated unions:

See this content in the original post

I don't know what impact using records has on performance, maybe that will be another post?

Summary

In this post, we have seen a few styles of single case discriminated unions and records to wrap up primatives. It is pretty trivial to use any of the approaches suggested in this artlcle and will give you additional type safety over that given by a raw staticaly typed primative, particularly when you have rules that define the type.

As always, let me know what you think about this post or any suggestions about future posts. https://twitter.com/ijrussell