Understanding Elm: Signals, Mailboxes, Addresses, and Actions

Brandon Richey
7 min readJan 5, 2016

--

The Elm Tangram

Introduction

Where we left off in our Introduction to Elm tutorial, we had built a nice little UI around our Elm todo list app, but we need to expand it a bit more. Right now, our app has a shell but nothing really running at its core, so that’s going to be the first area we’re going to have to address. But before we can address that, we need to have a more concrete understanding of what truly makes an Elm application, so we’ll explore that via mailboxes, addresses, actions, and signals!

The end goal is that we will be able to add a text box for the person’s name that is working with our todo list and have it update as the user types. We’ll get to the code more in-depth in Part 2 of this; right now we’re just going to focus on the core concepts.

Current Version

Elm: v0.16.0

Adding Interaction

In addition to basic types and functions, Elm has a few special concepts that are needed to piece together an interactive Elm application. To truly add a level of responsive interactivity to our application, we need to understand Signals, Mailboxes, and Addresses. These are concepts which at first glance sound super complicated, but when you get used to the concepts, they start to make a lot of sense (and, if you’re anything like me, you start to question why you would ever do things in another way).

Why Do We Need These?

You’re probably wondering, especially if you’re new to Elm, why we even need to do this? We have our model already set up, and it’s already being piped into our view. Can’t we just modify the model and call it a day?

Well, we can’t for a few reasons. One: the model is immutable, and what we’ve piped into our view is an immutable value, so that view function is going to render once and not again. What we really need is some sort of model that allows us tell the view “hey, EVERY time that this value changes, you need to update the view, too”. This is a major part of the Elm mentality: things change over time, and change over time needs to be represented in a way that doesn’t introduce crazy mutability.

Why are we worried about immutability? Mutability is the ability for something to change on us at any time. Immutability, as you may’ve guessed, is the guarantee that something won’t change on us. This makes it easier for us to make guarantees about the state of our application; there are no weird side-effects when someone changes a value without us expecting that change!

We solve this problem in Elm with our concept of mailboxes, addresses, actions, and signals, so we end up with a diagram that looks like the following:

What is a Mailbox?

A mailbox is exactly what it sounds like: a place to send/retrieve messages from. It’s a bin that we put messages in, which means that the inbox changes over time (this is a critical part of understanding Elm; we’ll explain in greater detail about that when we talk about Signals). We also need to tell Elm WHAT the messages are that are stored in the mailbox. Try not to get bogged down in the details of what’s going on in here yet and try to focus on the simplest explanation of what each of these components is.

What is an Address?

An address tells us WHERE the mailbox is located. Think about an application that talks to multiple mailboxes; how would it know which one you are referencing? The address just tells us which mailbox to store our new message in.

What is a Signal?

Signals are probably one of the hardest areas of Elm to understand at the start, but as soon as you get them it just kind of clicks and you understand it forever! It’s a bit of a cliché, but it truly is a lot like riding a bike. So, what is a Signal?

A Signal is just a value that changes over time. Think of it like a list, where each of the elements in that list are a variable’s value over time. Let’s demonstrate simply:

We’ll start with an initial value of a string that starts blank, so we’re working with a Signal String. The node in bold represent the current state of that signal.

Initial State: ""[ "" ] ------------------------------------------------------>

Next, something happens that changes the value of that string to “Hello”:

[ "" ] -> [ "Hello" ] --------------------------------------->

Notice that we never changed that initial value! Instead, we’re keeping track of EVERY value that string has ever had. The beauty of this is that we’ve also made these variables immutable, but we can still change them! When a function receives a signal’s value, it’s receiving an unchanging state, whether it is “” or “Hello”. Now we’ll change it again to say “Hello World”:

[ "" ] -> [ "Hello" ] -> [ "Hello World" ] ----------------->

Try not to get too carried away when you see type specs and declarations of Signal.Mailbox Action. Again, it seems incredibly complicated until you dig into it; if we tease it apart using the examples above, it becomes much simpler to understand: A Mailbox that stores Actions. Since a Mailbox changes over time, it has to be a Signal. Signals provide the Mailbox functionality from its libraries, so all we’re saying is our Mailbox stores Actions (I’ll talk about Actions later) and it changes as we add more messages onto it!

TL;DR

A mailbox has an address we can send messages to. Messages themselves are signals (values that change over time). We use mailboxes and signals to modify our application’s current state over time.

Let’s Talk About Actions

I talked a bit about Actions above, so let’s explore that a little more! You’ll typically see Actions in an Elm app referred to in the following format:

type Action = NoOp | UpdatePerson

What we’re doing here is defining a “Union Type”. It’s similar to an Enum if you’re comfortable with that sort of language. What we’re saying is that any time you see an “Action”, you should expect one of two values: “NoOp” or “UpdatePerson”. NoOp/UpdatePerson become Constructor Functions which return a NoOp or UpdatePerson Action. Since they’re constructors, we can also pass in initial values for those Actions. For example, it’s common to see Actions that follow this format:

type Action = NoOp | UpdatePerson String

This just means that whenever we initialize a “UpdatePerson” action, we need to pass it a string as well.

Updating our Model with Actions

We talk a lot about Actions because they’re the primary way we can modify our model. The model value itself is immutable, but if we pass in a Signal of a Model, it can change over time. Typically you’ll see a function written that takes in an action and returns a modified model as a result. Let’s take a look at an example:

update : Action -> Model -> Model
update action model =
case action of
NoOp ->
model
UpdatePerson newName ->
newName

There’s a lot going on here, so we’ll tease it apart again piece by piece. First, let’s look at our type specification.

update : Action -> Model -> Model

We have a function named update, and we pass in two arguments: Action and Model. Finally, we return out a Model. So what are we doing? We’re saying “the update function copies a value from an initial state to a new state based on whatever action we throw in, and returns out a modified copy”.

update action model =

As such, our function definition matches our type spec. We pass in an Action (via the action variable) and our initial model state (via the model variable).

case action of
NoOp ->
model
UpdatePerson newName ->
newName

Next, we have a case statement to figure out what action we’re supposed to be acting on. A case statement takes in a variable and then does something called pattern matching to figure out which anonymous function to execute. It finds the first value that matches what you pass in to the case statement. The arrow after the value says that this is an anonymous function to execute.

In the case of a NoOp action, we’re saying it doesn’t do anything, so it should just take the model and return it out, unchanged. In the case of an UpdatePerson value, remember that we defined our UpdatePerson action as an Action that initializes with a value, so we are going to return a modified copy of that initial value.

Next Steps

This was already pretty heavy (and the next part is going to be equally so), so we’ll pause here with a good understanding of what’s going on behind the scenes and understanding the core concepts that make Elm tick. In the next post we’ll actually start putting everything together and build the responsive portion of our web app! If you’re still wanting to learn a little more about signals in an Elm application you can watch through https://www.youtube.com/watch?v=FSdXiBLpErU.

In addition, now you can follow along the actual code for this and even more explanation of the core concepts described here at Part 2 of the Understanding Elm series: Applying Signals and Mailboxes to our Elm App

--

--