Introduction to Elm, v2

Custom Types in Messages

Richard Feldman

Richard Feldman

Vendr, Inc.
Introduction to Elm, v2

Check out a free preview of the full Introduction to Elm, v2 course

The "Custom Types in Messages" Lesson is part of the full, Introduction to Elm, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Through the use of pagination in the example app, custom types are shown to be a boon when applied to messages.


Transcript from the "Custom Types in Messages" Lesson

>> Richard Feldman: Let's move on to Pagination. Guess some people call it Pagination, I don't know, I always call it Pagination, that's nothing to do for the purpose of this workshop. So basically we gonna have a list of numbers, each of them representing a different page at the bottom of our feed of articles.

And when you use clicks on one, we want to load a new page of articles, all right. So, just a recap, currently we have tags influencing, and when you click on one of the tags, that's our only form of interaction. Clicking on a tab sends a message from view to update, through the Elm runtime.

Update receives this message and says I see you have a description and clickedTag, and you have is let's say I clicked on the cars tag this is great I'm gonna update that to be our selected tag. Which now we can do using our fancy custom type and have it render the appropriate tab accordingly.

We also have a type alias for that called type alias message which says description is a string and data is a string, which we did in the previous exercise. And now we're gonna want to introduce a new message for pagination. So when the user clicks on one of these inside of our view, we're gonna want to specify another one of these records that's going to say, hi.

Here was the page that the user clicked on, here's the description that says they clicked on a page. So we would have description = ClickedPage, Data = 2,and we have a problem. So if I click on page two, that's kind of what I want to happen in that situation but we said in our message that data is a string.

And I'm sure enough data is a string here, it needs to be a string in order for the functionality we already built to continue working. But here we've said that data is an int. It can't be both, [LAUGH] It's gonna be one or the other. As previously noted its really important that message have a consistent type between view and update, and certainly means that different parts of view need to agree on a common format for this message.

So one way we could fix this is we could say we're gonna add some more fields messages. We shall have string data which is the string and int data which is int, and then we're gonna be like, okay, for ClickedTag we're gonna have a stringData is cars like before.

IntData is, I don't know, it's not useful here so maybe it's -1? Or, and then for ClickedPage we'll have, stringData is not useful, so I guess it will be empty string. Then intData is 2, but you don't have to think very far into the future to realize this is not gonna scale very well.

Like, every time I add a new type of message to my program, I need to go through and add an additional field to the message, and then every single other message everywhere in my whole program, add another field to that. And most of the time, it's gonna be nonsense, so I have to pick a nonsense value to use.

This is where we're gonna get out of hand. This is really where we sort bumped up against the boundary of why using records for message is not really the best choice. So in practice people don't actually use records for message. I just did that as a way to sort of get us up and running.

In contrast, let's think about what happens if we use a custom type for this. Now we can say, we have two variants of message, ClickedTag, which holds on to a string, and ClickedPage, which holds on to an int. This precisely describes every single possibility that we could have for interaction on our program.

Either the user clicked a tag. In which case, not only do we know they clicked it, we also know which tag they clicked, cuz we have that string held on to inside the message. Or, they clicked a page, in which case, we have an int representing the page they clicked.

If we add more types of interactions, that's fine. We can just keep adding variants, each of which holds exactly the additional, contextual information necessary, for update to do its job. Great, now this whole record gets replaced by ClickedTag cars, and this whole record gets replaced by ClickedPage 2.

That's it. So now, if this is our new message type, we've gotten ride of the old type alias message and now we're using a custom type for it. Update can say case msg of ClickedTag, in which case we've got the selectedTag and we're going to destructure that in there and give it a name just as if it were a function argument.

And now we can inside the body of this branch, use the selectedTag. Or ClickedPage which has the page number which is an int, and then we can use that in here. Now what's especially cool about this, is that we don't have to put a default branch like before we had to have that else return the bottle on change that is even come up here.

The best part of this is that if we keep adding message types and we ever forget to handle one and update, it won't compile. Elm will say, hey, you introduced a new message type. You're using that, but you didn't actually write the logic to handle it. You need to add a branch to that case expression, to handle the possibility that the user does that interaction.

Also, if we ever delete one, it will once again give us an error. Also, if we add one that's redundant with a previous one, we already handled that somewhere else, it'll gives us an error. So, for many reasons, not just the number of fields that we have to put in a record to try and scale that out.

Custom types are just a, they fit the use case of messages like a glove. They're just the absolute best thing we could use to represent this. And it's sort of a wonderful language feature that Elm has that allows this to work. Little known fact about Redux, if you're familiar with Redux, you may have, this may seem kind of familiar.

That's because Redux is actually based on the Elm architecture. The history of Redux is that Dan Alvarov was giving a talk at a conference, wanted to implement time travel debugging and hot reloading, which is something that Elm already had at the time. And so he looked at Elm and tried to figure out how Elm was able to do that, and the answer was this architecture.

But of course, JavaScript does not have custom types. So he came up with an alternative that's based on using JavaScript objects instead. But some of the complaints that people have about Redux, don't actually apply to the original Elm because, when you have custom types a lot of those things just don't exist.

Okay, so to use this to implement our pagination bar down here, we can write a page button on the view side that says I'm gonna take an int for the current. Page number that I'm going to display, it's going to return HTML message. It'll take that pageNumber as an argument, and we'll say instead of onClick, and then passing the description record like we did before.

Now we will just say ClickedPage pageNumber, and that will return the message that we want. Then that's what we'll get sent to update. And then the text is just String.fromInt pageNumber. And now you've successfully connected view and update, using our nice custom types to link everything together.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now