Undefined Rants

Code, Ramen and Avocado

Time Travel Back to Understand Elm Mailbox

Imagine that you’re a military colonel fighting a war back in the ancient time. You have a base camp but since you are fighting outside of home base, the only way to communicate with your commander is through the homing pigeons. As the war proceed, your great commander will send you military tactics for you to act accordingly to the plan. In order to notify you, your commander has to know your address, which he clearly does, and in addition to that, he will attached signal (text, cipher etc…) to the pigeons and let it fly to you. This is how you win the war without missing any instruction from your commander!

Coincidently, this is how Mailbox in Elm works too.

In this post, we will walk through Elm mailbox by implementing a web app for your commander to order his command.

Prequisite

This tutorial assume that you are familiar with Elm Signal. If you need a refresher I invite you to read A Step-to-step Guide to Elm Signal which I published few days ago. Also because this tutorial use elm-html package, so a local environment for Elm to work is required. In case you don’t I have got you covered. Just make sure your elm-packge.json contains elm-html package.

Also note that the code showns are only tested with Elm 0.16

Mailbox

In Elm, Mailbox contains of 2 elements, an address to receive message and a signal which is the content of message.

1
2
3
4
type alias Mailbox a =
    { address : Address a
    , signal : Signal a
    }

Here, signal can be any type – String, Int, Action(custom type). Let’s go back to the ancient war to dive in how Mailbox works. You know that your commander will send you a command(Action) from the list he gave you:

1
type Action = NoOp | Attack | Defense

And this is how you want to response with:

1
2
3
4
5
6
7
8
type alias Model = String

update : Action -> Model -> Model
update action model =
  case action of
    NoOp -> model
    Attack -> "Attacking"
    Defense -> "Defensing"

You will just response by letting your army know by displaying it. Because we will just diplay some relevant text so our model will just be string. When your army do not need to attack nor defense, you want them to rest as much as possible.

1
2
initialModel : Model
initialModel = "Resting"

And for your commander, he has a high tech admin panel which contains of 2 button – Attack and Defense and another place to see what is the current activity of army.

1
2
3
4
5
6
7
8
9
10
11
12
13
view : String -> Html
view command =
  div [ boardStyle ]
  [
    div []
    [ span [] [ text "Army status:"]
    , strong [] [ text command ]
    ],
    button []
    [ text "Attack" ],
    button []
    [ text "Defense" ]
  ]

So this will be how your general’s command panel looks:

And this is the code which just display it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import Html exposing (..)
import Html.Events exposing (..)
import Html.Attributes exposing (..)

type alias Model = String

initialModel : Model
initialModel = "Resting"

type Action = NoOp | Attack | Defense

update : Action -> Model -> Model
update action model =
  case action of
    NoOp -> model
    Attack -> "Attacking"
    Defense -> "Defensing"

view : String -> Html
view command =
  div [ boardStyle ]
  [
    div []
    [ span [] [ text "Army status:"]
    , strong [] [ text command ]
    ],
    button []
    [ text "Attack" ],
    button []
    [ text "Defense" ]
  ]

boardStyle =
  style
    [ ("width", "200px")
    , ("margin", "auto")
    , ("marginTop", "200px")
    ]

main : Html
main =
  view initialModel

I also add some style for display the div on center.

By now, your commander cannot command because he doesn’t has a address to send his command. This is where Mailbox comes in. Let’s start by initializing a mailbox with NoOp Action which does nothing.

1
2
3
mailbox : Signal.Mailbox Action
mailbox =
  Signal.mailbox NoOp

Now to send message to this mailbox, we will access the address by commands.address. We want to send an action to the address when button is pressed. Because we are using elm-html package, it provides handy event handler like onClick.

1
onClick : Address a -> a -> Attribute

onClick takes 2 arguments, an address and any type(in our case, Action) and return an Attibute. Here is how our Attack button will be:

1
2
3
button
  [ onClick address Attack ]
  [ text "Attack" ]

It’s important to note that onClick is just a nice wrapper of on provided by the elm-html package. Here is how on is being defined:

1
on : String -> Json.Decoder a -> (a -> Signal.Message) -> Attribute

From the doc, we can see how onClick is defined:

1
2
3
4
5
import Json.Decode as Json

onClick : Signal.Address a -> Attribute
onClick address =
    on "click" Json.value (\_ -> Signal.message address ())

It uses Signal.message to send values to the defined addess.

Our view function need to know the address of the mailbox it wants to deliver, so let’s do some refactoring:

1
2
3
4
5
6
7
8
9
10
11
12
13
view : Signal.Address Action -> String -> Html
view address command =
  div [ boardStyle ]
  [
    div []
    [ span [] [ text "Army status:"]
    , strong [] [ text command ]
    ],
    button [ onClick address Attack ]
    [ text "Attack" ],
    button [ onClick address Defense ]
    [ text "Defense" ]
  ]

It nows take an extra argument – address.

To access the content of mailbox, which is the command from commander, we will use commands.signal.

1
2
3
commands : Signal Action
commands =
  mailbox.signal

Also we need a model to accumulate current state:

1
2
3
model : Signal Model
model =
  Signal.foldp update initialModel commands

Finally to display to our army, we need to update our main function.

1
2
3
main : Signal Html
main =
  Signal.map (view mailbox.address) model

To visualize how Signal is working in the app:

1
2
3
4
5
6
7
8
9
10
                commands = mailbox.signal
Signal Action:  -----Attack-----Defense-----Rest---------|->

         model = Signal.foldp update initialModel commands     

Signal Model:   --"Attacking"--"Defensing"--"Resting"----|->

                    view mailbox.address

Signal Html:    <"Attacking">-<"Defensing">-<"Resting">--|->

Well well well, now our commander can offer his great army to conquer the world.

In case you miss something, here is the code so far:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import Html exposing (..)
import Html.Events exposing (..)
import Html.Attributes exposing (..)

type alias Model = String

initialModel : Model
initialModel = "Resting"

type Action = NoOp | Attack | Defense

update : Action -> Model -> Model
update action model =
  case action of
    NoOp -> model
    Attack -> "Attacking"
    Defense -> "Defensing"

view : Signal.Address Action -> String -> Html
view address command =
  div [ boardStyle ]
  [
    div []
    [ span [] [ text "Army status:"]
    , strong [] [ text command ]
    ],
    button [ onClick address Attack ]
    [ text "Attack" ],
    button [ onClick address Defense ]
    [ text "Defense" ]
  ]

boardStyle =
  style
    [ ("width", "200px")
    , ("margin", "auto")
    , ("marginTop", "200px")
    ]

mailbox : Signal.Mailbox Action
mailbox =
  Signal.mailbox NoOp

commands : Signal Action
commands =
  mailbox.signal

model : Signal Model
model =
  Signal.foldp update initialModel commands

main : Signal Html
main =
  Signal.map (view mailbox.address) model

Exercise

Seems like we are winning a lot of fight but our army looks tired. I am glad you are why. Because they never rest since they start attacking or defending. Here’s a task for you, create a button to let general do that. Good luck !

Conclusion

So in this post, we created a mailbox which contains of an address and incoming signal which let our general to do his command. Do you now realize why do we need a mailbox ? Yes to let outside world to communicate with us. For instance: user inputs. I hope that this post solve the mystery of what Elm start app is doing. If you understand Signal and Mailbox, you can implement your own start app.

Credit

Thanks Sharon for some grammar fixing in the introduction part.

Comments