Clone the code or follow along in the online editor.

We are about to make an app that "rolls dice", producing a random number between 1 and 6.

When I write code with effects, I usually break it into two phases. Phase one is about getting something on screen, just doing the bare minimum to have something to work from. Phase two is filling in details, gradually approaching the actual goal. We will use this process here too.

Phase One - The Bare Minimum

As always, you start out by guessing at what your Model should be:

type alias Model =
  { dieFace : Int

For now we will just track dieFace as an integer between 1 and 6. Then I would quickly sketch out the view function because it seems like the easiest next step.

view : Model -> Html Msg
view model =
  div []
    [ h1 [] [ text (toString model.dieFace) ]
    , button [ onClick Roll ] [ text "Roll" ]

So this is typical. Same stuff we have been doing with the user input examples of The Elm Architecture. When you click our <button> it is going to produce a Roll message, so I guess it is time to take a first pass at the update function as well.

type Msg = Roll

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Roll ->
      (model, Cmd.none)

Now the update function has the same overall shape as before, but the return type is a bit different. Instead of just giving back a Model, it produces both a Model and a command. The idea is: we still want to step the model forward, but we also want to do some stuff. In our case, we want to ask Elm to give us a random value. For now, I just fill it in with Cmd.none which means "I have no commands, do nothing." We will fill this in with the good stuff in phase two.

Finally, I would create an init value like this:

init : (Model, Cmd Msg)
init =
  (Model 1, Cmd.none)

Here we specify both the initial model and some commands we'd like to run immediately when the app starts. This is exactly the kind of stuff that update is producing now too.

At this point, it is possible to wire it all up and take a look. You can click the <button>, but nothing happens. Let's fix that!

Phase Two - Adding the Cool Stuff

The obvious thing missing right now is the randomness! When the user clicks a button we want to command Elm to reach into its internal random number generator and give us a number between 1 and 6. The first step I would take towards that goal would be adding a new kind of message:

type Msg
  = Roll
  | NewFace Int

We still have Roll from before, but now we add NewFace for when Elm hands us our new random number. That is enough to start filling in update:

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    Roll ->
      (model, Random.generate NewFace (Random.int 1 6))

    NewFace newFace ->
      (Model newFace, Cmd.none)

There are two new things here. First, there is now a branch for NewFace messages. When a NewFace comes in, we just step the model forward and do nothing. Second, we have added a real command to the Roll branch. This uses a couple functions from the Random library. Most important is Random.generate:

Random.generate : (a -> msg) -> Random.Generator a -> Cmd msg

This function takes two arguments. The first is a function to tag random values. In our case we want to use NewFace : Int -> Msg to turn the random number into a message for our update function. The second argument is a "generator" which is like a recipe for producing certain types of random values. You can have generators for simple types like Int or Float or Bool, but also for fancy types like big custom records with lots of fields. In this case, we use one of the simplest generators:

Random.int : Int -> Int -> Random.Generator Int

You provide a lower and upper bound on the integer, and now you have a generator that produces integers in that range!

That is it. Now we can click and see the number flip to some new value!

So the big lessons here are:

  • Write your programs bit by bit. Start with a simple skeleton, and gradually add the tougher stuff.
  • The update function now produces a new model and a command.
  • You cannot just get random values willy-nilly. You create a command, and Elm will go do some work behind the scenes to provide it for you. In fact, any time our program needs to get unreliable values (randomness, HTTP, file I/O, database reads, etc.) you have to go through Elm.

At this point, the best way to improve your understanding of commands is just to see more of them! They will appear prominently with the Http and WebSocket libraries, so if you are feeling shaky, the only path forward is practicing with randomness and playing with other examples of commands!

Exercises: Here are some that build on stuff that has already been introduced:

  • Instead of showing a number, show the die face as an image.
  • Add a second die and have them both roll at the same time.

And here are some that require new skills:

  • Instead of showing an image of a die face, use the elm-lang/svg library to draw it yourself.
  • After you have learned about tasks and animation, have the dice flip around randomly before they settle on a final value.

results matching ""

    No results matching ""