How to extract values from union types in Elm

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

In summary, if you have a union type with multiple tags, then you need to use a case expression to extract the contained values. If you have a single tag union type, then you can use destructuring as a shortcut when defining functions or writing let expressions.

Comments or questions? I'm @alexkorban on Twitter.