Elm is a really interesting language to develop applications for the browser. It's quite young but already proven in production. This blog post will attempt to give an introduction to signals, mailboxes and interop. These three fundamentals are necessary for doing more with your app then just the very basics. One good use case is working together with existing javascript code. For that we will take the first example in the elm architecture tutorial. And look at the steps required to get an elm event in javascript. Also take a look at some of the other blogs and resources on the web.

Mailbox

The mailbox can be compared to an event dispatcher or flux store. But it handles just one type of event, for other type of events you create more mailboxes. Perhaps therefor a comparison to go's channels or linux pipes would be better. It receives incoming events on it's address and emits them using it's signal. Or in other words: you can send events to it's address and act on it's signal (just as a callback would act on a javascript event). The purpose is to control the flow of events in your program. It's also very much related to the publish-subscribe pattern. Hopefully you, the reader, are familiar with one of these technologies for a better understanding, if not just read on and try the code examples later on.

In elm it's required for the signal to carry along some data. Because you can subscribe to the mailbox's signal, without having to send something to it's address first, the mailbox needs a default value. The type of value must be defined on the mailbox and can not be changed during runtime.

The simplest piece of data you can use is an empty tuple (). Which can be compared to void in C/C++. It's also possible to optionally send some data. Maybe this is a bit counter-intuitive as no data is still a value represented by Nothing. Nothing is similar to a null value in other language. To make data optional use Maybe, in this case the type will be Maybe (). The value of type Maybe () can only be Just () or Nothing. Let's define a mailbox now using type Maybe () and default value Nothing.

testMailBox :
  { address : Signal.Address (Maybe ())
  , signal : Signal (Maybe ())
  }
testMailBox = Signal.mailbox Nothing

Place this code in Counter.elm of example 1 of elm's architecture tutorial. The type signature here is optional as it can be infered by the compiler.

Address

After creation of the mailbox the address is now available as testMailBox.address. Let's use this address by replacing

[ button [ onClick address Decrement ] [ text "-" ]

with

[ button [ onClick testMailBox.address Nothing ] [ text "-" ]

Now we have a trigger to post something at the address of the mailbox. We could also have defined a timer as trigger, but this way we will have manual control by pressing the decrement button. Nothing is the value we are sending here, which override the default mailbox value which was also Nothing. However we could send much more interesting data along if needed.

Why not hook into the existing mailbox and use that signal you might ask? Well for the sake of explaining mailboxes this is the easier route. Because the address of the original code is defined in the startapp package. There, a list of regular values is mapped over to produce a list of signals .. which are then merged together.

Signals & Ports

Now that we have a mailbox handling our event let's use it's signal to send the event to vanilla javascript. For this we need to make a port. A port is a function definition which gives back a Signal, but with the keyword port in front of it. The simplest example of port would be this one

port testPort = Signal.constant ()

Using the Signal.constant function allows us to wrap a normal value as a signal. Only problem is since signals are made for events and a constant is not an event this code never triggers.

ports can only be defined in the Main module, so let's make the testMailBox available by changing the first line in Main.elm to

import Counter exposing (update, view, testMailBox)

Now let's feed our mailbox signal directly into the port

port testPort : Signal (Maybe ())
port testPort = testBox.signal

Type signatures are required and should be right above the function definition.

Javascript

To receive the event in javascript we need to subscribe to the port. Instead of browsing to Main.elm directly, let's make an index.html file which uses Main.elm to display the app. That way we can define our own html, css and js. The example below assumes you are using elm-reactor, which will serve up debug.js for you.

<html>
  <head>
    <meta charset="UTF-8">
  </head>
  <body>
    <script src="elm.js"></script>
    <script src="/_reactor/debug.js"></script>
    <script>
function testPortCallback(val) {
  console.log(val);
}
var app = Elm.fullscreen(Elm.Main);
app.ports.testPort.subscribe(testPortCallback);
    </script>
  </body>
</html>

Now go ahead and try the simple example. Every time you click + the counter increases as it did in the original code. But when pressing - a value is printed to the console. In this case null since Nothing was used in elm code.

Inbound ports

So far everything has been covered to create an outbound port (from elm to js). Let's add an inbound port (from js to elm) as well. In this post we use Startapp.Simple which does not allow sending more signals into it's start function. Therefor we are going to modify the Startapp.Simple just a little bit. The fullblown Startapp does support more signals. But this code is also more complex, plus it doesn't hurt to get a little bit familiar with the Startapp.Simple code.

The first thing todo is remove

import StartApp.Simple exposing (start)

from Main.elm and copy-paste all the code from Startapp.Simple to your Main.elm except for the first line starting with module.

With that out of the way let's add the port first, before tying it in with rest of the code. Add this to Main.elm

port inboundPort : Signal (Maybe Bool)

Below you can find the part where the update function is called, we are going to modify this part so that our event from javascript can be handled by the same function.

model =
  Signal.foldp
    (\(Just action) model -> config.update action model)
    config.model
    actions.signal

Notice that actions.signal (which is the signal from a mailbox) goes into a lambda function (starting with (\). From here it's passed to the update function (config.update in the code). We want the signal from the port to also go into the update function. But let's think first .. what should our event actually do? Well since we "broke" the Decrement action before, let's add it back, but this time using an event originating from javascript. Below is the html you can use for this.

<button id="fromJS">Decrement</button>
<script>
var app = Elm.fullscreen(
  Elm.Main,
  {
    inboundPort: null
  }
);
document.getElementById('fromJS').addEventListener('click', function() {
  app.ports.inboundPort.send(null);
});
</script>

Notice that just as the outbound port needed a default value, so does the inbound port. Only this default value is supplied in javascript and passed as object to the second argument of Elm.fullscreen. We initialize with null and send null which will be represented as Nothing in elm. The event is still being send but the value of the payload data is just null. Since our Decrement action is not a type that can be represented in javascript we need to "convert" the event value into an action.

Signal.map (\_ -> Just Counter.Decrement) inboundPort

\_ means we don't care what the specific value of inboundPort was (which could be true, false or null). Since we are going to use this port just to notify that the button has been clicked. We have to place Just in front of Counter.Decrement since the lambda function (the one the line after Signal.foldp) is going to pattern match on Just action. Now finally let's merge the signal from the original mailbox together with our port signal by replacing actions.signal with

(Signal.merge actions.signal (Signal.map (\_ -> Just Counter.Decrement) inboundPort) )

We are left with one small problem and that is, since we specified the type of the signals explicitly (by using Counter.Decrement, the type signature from the start function should change to

start : Config model Counter.Action -> Signal Html

Conclusion

The blog post shows a full working example of both inbound and outbound ports. It's meant to complement other guides and the official documentation. Even if you don't understand everything yet, you can already start applying the techniques to expand your own code.

In summary
The notation of mailbox is like a record, which has two items: an address and a signal. Addresses are used to send events to, while signals can be used to respond to events. Ports can be used to send and recieve events from javascript.