Union types are everywhere in Elm, but when you’re starting to learn the language, they can be a bit unfamiliar and confusing, because they don’t really exist in other languages like JavaScript or C#.

The main question I’ve seen people ask is: how do I extract a value from a union type?

Suppose you have a union type like this:

type UserId = Id Int | Uuid String

A UserId value can contain either an Int or a String . The solution for getting to those values is simple because Elm provides only one tool for that - the case expression:

getUser : UserId -> User getUser userId = case userId of Id id -> getUserById id Uuid id -> getUserByUuid id

Given a userId of type UserId , we need to write a case expression which accounts for every possible tag in the union type, and in each of those branches we can name the contained value (I called it id in both branches) and then use it. In my case, I pass the id to these functions:

getUserById : Int -> User getUserByUuid : String -> User

So, whenever you want to get the value in a variable of a union type, you need to write a case expression.

One special case

Another common pattern in Elm is to define new types like this:

type UserName = UserName String

This is different from defining a type alias and is a useful technique which I discuss in my upcoming book.

The interesting thing about it is that it’s not another language construct but rather a special case of union types. It’s just a regular old union type that only contains one tag - in this case UserName , the same as the name of the type itself, which is totally cool in Elm.

How do we extract the string from a value of type UserName ? Well, we can use a case expression like we did before:

getName : UserName -> String getName userName = case userName of UserName s -> s getName <| UserName "Joe" -- returns "Joe"

However, this does look a bit silly, so in this situation, Elm provides a convenient shortcut. We can write getName like this instead:

getName (UserName s) = s getName <| UserName "Joe" -- returns "Joe"

Here, we are using destructuring directly in the argument list of the function in order to name the contained string.

In case you are wondering, if you try to do this when the union type has multiple tags, the compiler will complain.

Destructuring works in let expressions as well:

toUpper : UserName -> String toUpper userName = let (UserName s) = userName in String.toUpper s

Finally, it works in anonymous functions too (there is very little difference between anonymous and named functions):

(\(UserName s) -> s) (UserName "Joe") -- returns "Joe"

There are a lot more uses for destructuring, and it’s something I also talk about in great detail in my upcoming book.

Summary