Note: Custom types used to be referred to as “union types” in Elm. Names from other communities include tagged unions and ADTs.

Custom Types

So far we have seen a bunch of types like Bool, Int, and String. But how do we define our own?

Say we are making a chat room. Everyone needs a name, but maybe some users do not have a permanent account. They just give a name each time they show up.

We can describe this situation by defining a UserStatus type, listing all the possible variations:

type UserStatus = Regular | Visitor

The UserStatus type has two variants. Someone can be a Regular or a Visitor. So we could represent a user as a record like this:

type UserStatus
  = Regular
  | Visitor

type alias User =
  { status : UserStatus
  , name : String
  }

thomas = { status = Regular, name = "Thomas" }
kate95 = { status = Visitor, name = "kate95" }

So now we can track if someone is a Regular with an account or a Visitor who is just passing through. It is not too tough, but we can make it simpler!

Rather than creating a custom type and a type alias, we can represent all this with just a single custom type. The Regular and Visitor variants each have an associated data. In our case, the associated data is a String value:

type User
  = Regular String
  | Visitor String

thomas = Regular "Thomas"
kate95 = Visitor "kate95"

The data is attached directly to the variant, so there is no need for the record anymore.

Another benefit of this approach is that each variant can have different associated data. Say that Regular users gave their age when they signed up. There is no nice way to capture that with records, but when you define your own custom type it is no problem. Let's add some associated data to the Regular variant in an interactive example:

[ { "add-type": "User", "input": "type User\n = Regular String Int\n | Visitor String\n" }, { "input": "Regular", "value": "\u001b[36m<function>\u001b[0m", "type_": "String -> Int -> User" }, { "input": "Visitor", "value": "\u001b[36m<function>\u001b[0m", "type_": "String -> User" }, { "input": "Regular \"Thomas\" 44", "value": "\u001b[96mRegular\u001b[0m \u001b[93m\"Thomas\"\u001b[0m \u001b[95m44\u001b[0m", "type_": "User" }, { "input": "Visitor \"kate95\"", "value": "\u001b[96mVisitor\u001b[0m \u001b[93m\"kate95\"\u001b[0m", "type_": "User" } ]

Try defining a Regular visitor with a name and age ⬆️

We only added an age, but variants of a type can diverge quite dramatically. For example, maybe we want to add location for Regular users so we can suggest regional chat rooms. Add more associated data! Or maybe we want to have anonymous users. Add a third variant called Anonymous. Maybe we end up with:

type User
  = Regular String Int Location
  | Visitor String
  | Anonymous

No problem! Let’s see some other examples now.

Messages

In the architecture section, we saw a couple of examples of defining a Msg type. This sort of type is extremely common in Elm. In our chat room, we might define a Msg type like this:

type Msg
  = PressedEnter
  | ChangedDraft String
  | ReceivedMessage { user : User, message : String }
  | ClickedExit

We have four variants. Some variants have no associated data, others have a bunch. Notice that ReceivedMessage actually has a record as associated data. That is totally fine. Any type can be associated data! This allows you to describe interactions in your application very precisely.

Modeling

Custom types become extremely powerful when you start modeling situations very precisely. For example, if you are waiting for some data to load, you might want to model it with a custom type like this:

type Profile
  = Failure
  | Loading
  | Success { name : String, description : String }

So you can start in the Loading state and then transition to Failure or Success depending on what happens. This makes it really simple to write a view function that always shows something reasonable when data is loading.

Now we know how to create custom types, the next section will show how to use them!

Note: Custom types are the most important feature in Elm. They have a lot of depth, especially once you get in the habit of trying to model scenarios more precisely. I tried to share some of this depth in Types as Sets and Types as Bits in the appendix. I hope you find them helpful!

results matching ""

    No results matching ""