Ways of Creating Single Case Discriminated Unions in F#

In this post, I will go through a number of the approaches that I have seen of creating single case discriminated unions in F#.

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:

type CustomerId = CustomerId of Guid

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

let id = CustomerId (Guid.NewGuid())
let (CustomerId value) = id

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:

type CustomerId = private CustomerId of Guid

// Problem outside of current module
let id = CustomerId (Guid.NewGuid())
let (CustomerId value) = id

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:

type CustomerId = private CustomerId of Guid

module CustomerId =
    let New() = CustomerId (Guid.NewGuid())
    let Value (CustomerId value) = id

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

let id = CustomerId.New()
let value = CustomerId.Value id

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:

type CustomerId = private CustomerId of Guid
    with
        member this.Value =
            let (CustomerId value) = this
            value
        static member New() = CustomerId (Guid.NewGuid())

This now makes extracting the value much cleaner:

let id = CustomerId.New()
let value = id.Value

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:

type CustomerId = private CustomerId of Guid
    with
        member this.Value = let (CustomerId value) = this in value
        static member New() = CustomerId (Guid.NewGuid())

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

let id = CustomerId.New()
let value = id.Value

Another way is to use an anonymous function:

type CustomerId = private CustomerId of Guid
    with
        member this.Value = this |> fun (CustomerId value) -> value
        static member New() = CustomerId (Guid.NewGuid())

Again the usage is the same as before:

let id = CustomerId.New()
let value = id.Value

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:

type CustomerId = CustomerId of Guid with member this.Value = this |> fun (CustomerId value) -> value

Usage then looks like this:

let id = CustomerId (Guid.NewGuid())
let value = id.Value

Alternate Solution

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

module Identifier =
    type CustomerId = private CustomerId of Guid
        with
            static member New() = CustomerId (Guid.NewGuid())

    let (|CustomerId|) (CustomerId value) = value

Using the new module looks like this:

module OtherModule =
    open Identifier

    let id = CustomerId.New()
    let (CustomerId value) = id

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:

type CustomerId = { CustomerId : Guid }

Creation and value extraction look like this:

let id = { CustomerId = Guid.NewGuid() }
let value = id.CustomerId

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

type CustomerId = { CustomerId : Guid }
    with 
        member this.Value = this.CustomerId
        static member New() = { CustomerId = Guid.NewGuid() }

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

let id = CustomerId.New()
let value = id.Value

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

Blog 12/3/21

Using Discriminated Union Labelled Fields

A few weeks ago, I re-discovered labelled fields in discriminated unions. Despite the fact that they look like tuples, they are not.

Blog 3/10/21

Introduction to Web Programming in F# with Giraffe – Part 1

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 3/11/21

Introduction to Web Programming in F# with Giraffe – Part 2

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 3/12/21

Introduction to Web Programming in F# with Giraffe – Part 3

In this series we are investigating web programming with Giraffe and the Giraffe View Engine plus a few other useful F# libraries.

Blog 6/27/23

Boosting speed of scikit-learn regression algorithms

The purpose of this blog post is to investigate the performance and prediction speed behavior of popular regression algorithms, i.e. models that predict numerical values based on a set of input variables.

Two people discussing in front of a computer
Lösung 2/14/22

The COBOL Survival Team for IBM i (AS400)

COBOL developers on IBM i (AS400) are a rare commodity. PKS provides a powerful team especially for this application!

Headerbild zu Webserver mit Open Source
Technologie 11/12/20

Web server with Open Source

Web servers provide their application with the gateway to the world: this is where requests for data for a complex web app and resources for a website go in and out.

CLOUDPILOTS, Google Workspace, G Suite, Google Cloud, GCP, MeisterTask, MindMeister, Freshworks, Freshdesk, Freshsales, Freshservice, Looker, VMware Engine
Lösung

Cloud Transformation Use Cases

Together with Txture, CLOUDPILOTS guides you through Cloud migration. Below you will find different use cases for Txture.

Blog 9/27/22

Creating solutions and projects in VS code

In this post we are going to create a new Solution containing an F# console project and a test project using the dotnet CLI in Visual Studio Code.

Cloud Technologie Managed Service
Service

AUTOPILOT Business Class

Business is our upgrade to Economy. This version includes a few more features, such as the handling of incidents and standard changes (user, group, calendar and license management and Gmail settings) according to the fair use principle.

Blog 12/19/22

Creating a Cross-Domain Capable ML Pipeline

As classifying images into categories is a ubiquitous task occurring in various domains, a need for a machine learning pipeline which can accommodate for new categories is easy to justify. In particular, common general requirements are to filter out low-quality (blurred, low contrast etc.) images, and to speed up the learning of new categories if image quality is sufficient. In this blog post we compare several image classification models from the transfer learning perspective.

Blog 7/22/24

So You are Building an AI Assistant?

So you are building an AI assistant for the business? This is a popular topic in the companies these days. Everybody seems to be doing that. While running AI Research in the last months, I have discovered that many companies in the USA and Europe are building some sort of AI assistant these days, mostly around enterprise workflow automation and knowledge bases. There are common patterns in how such projects work most of the time. So let me tell you a story...

Blog 8/7/20

Understanding F# Type Aliases

In this post, we discuss the difference between F# types and aliases that from a glance may appear to be the same thing.

Blog 4/28/23

Creating a Social Media Posts Generator Website with ChatGPT

Using the GPT-3-turbo and DALL-E models in Node.js to create a social post generator for a fictional product can be really helpful. The author uses ChatGPT to create an API that utilizes the openai library for Node.js., a Vue component with an input for the title and message of the post. This article provides step-by-step instructions for setting up the project and includes links to the code repository.

Referenz 3/30/22

License and software consulting from a single source

TIMETOACT has been supporting the sports retailer for more than two years, not only for the support of the ILMT, but also for consulting on IBM Cognos - which was part of the IBM audit, among other things.

Blog 6/16/23

CSS :has() & Responsive Design

In my journey to tackle a responsive layout problem, I stumbled upon the remarkable benefits of the :has() pseudo-class. Initially, I attempted various other methods to resolve the issue, but ultimately, embracing the power of :has() proved to be the optimal solution. This blog explores my experience and highlights the advantages of utilizing the :has() pseudo-class in achieving flexible layouts.

Standort

Location of Winterthur

Find catworkx AG and IPG Information Process Group AG in Winterthur: Theaterstrasse 17, 8400 Winterthur

Blog 5/18/22

Introduction to Functional Programming in F#

Dive into functional programming with F# in our introductory series. Learn how to solve real business problems using F#'s functional programming features. This first part covers setting up your environment, basic F# syntax, and implementing a simple use case. Perfect for developers looking to enhance their skills in functional programming.

News

Proof-of-Value Workshop

Today's businesses need data integration solutions that offer open, reusable standards and a complete, innovative portfolio of data capabilities. Apply for one of our free workshops!

Headerbild Industrial Internet of Things (IIoT)
Kompetenz 9/16/20

Industrial Internet of Things

Whether in industry, urban planning or in the private sphere: The Internet of Things is making our lives easier. In particular, the digitalization of industrial production, saves companies time and money. We support you with your IoT project!

Bleiben Sie mit dem TIMETOACT GROUP Newsletter auf dem Laufenden!