# State machine-like Model advice

**URL:** <https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988>\
**Category:** Request Feedback\
**Created:** [February 23, 2021, 6:49am UTC](https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988 "2021-02-23T06:49:42Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![moosingin3space](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/moosingin3space/32/3857_2.png) [@moosingin3space](https://discourse.elm-lang.org/u/moosingin3space)\
**Post date:** [February 23, 2021, 6:49am UTC](https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988/1 "2021-02-23T06:49:42Z")

</div>

Hi, I’m new to Elm and am building a simple quiz UI (such as that of Buzzfeed) to teach myself the language and environment. I see my UI fitting into three distinct stages:

1. Initial (showing the “hello and welcome to the quiz” screen)
2. Quiz question (stores a running tally of options selected)
3. Complete (shows the winning option)

I’m trying to model this with Elm, but I keep running into a wall, due to the fact that only a subset of `Msg`s are applicable in each stage. In particular, the `Start` message only makes sense in the Initial and Complete stages, and the `Select` and `Next` messages only make sense in the Quiz stage. My current approach uses `Maybe` everywhere, even when it doesn’t make sense, because I can’t figure out how to model `Msg`s that are only applicable in a particular stage.

Any advice? Thank you in advance!

---

<div class="post-metadata">

**Author:** ![Lucas\_Payr](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/lucas_payr/32/4993_2.png) [@Lucas\_Payr](https://discourse.elm-lang.org/u/Lucas_Payr)\
**Post date:** [February 23, 2021, 7:26am UTC](https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988/2 "2021-02-23T07:26:26Z")

</div>

you will need to write a module for every page: `Page.Initial, Page.Quiz and Page.Complete`.

Each page contains its own Model/Msg/View/Update.

You then will need to wire the pages together. The go-to tool for this is [elm-spa](https://www.elm-spa.dev/).

* * *

But because you mentioned state machines, I’d like to recommend using [Orasund/elm-action](https://package.elm-lang.org/packages/Orasund/elm-action/latest/) instead. It’s a package I wrote to model Elm programs as state machines.

Here is how you would set up your `Main` module:

```auto
import Page.Initial as Initial
import Page.Quiz as Quiz
import Page.Complete as Complete
import Action

type Model =
  Initial Initial.Model
  | Quiz Quiz.Model
  | Complete Complete.Model

type Msg =
  | InitialMsg Initial.Msg
  | QuizMsg Quiz.Msg
  | Complete Complete.Msg

update : Msg -> Model -> (Model, Cmd Msg)
update mg ml =
  case (mg,ml) of
    (InitialMsg msg, Initial model) ->
      Initial.update msg model
      |> Action.config
      |> Action.withTrasition Quiz.init Quiz QuizMsg
      |> Action.withUpdate Initial InitialMsg
      |> Action.apply
    (QuizMsg msg, Quiz model) ->
      Quiz.update msg model
      |> Action.config
      |> Action.withTransition Complete.init Complete CompleteMsg
      |> Action.withUpdate Quiz QuizMsg
      |> Action.apply

    (CompleteMsg msg, Complete model) ->
      |> Action.config
      |> Action.withUpdate Complete CompleteMsg
      |> Action.apply 

    _ -> --Edit: added default case
      (ml,Cmd.none)

```

Checkout the [documentation](https://package.elm-lang.org/packages/Orasund/elm-action/latest/Action) for more information.

---

<div class="post-metadata">

**Author:** ![eike](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/eike/32/749_2.png) [@eike](https://discourse.elm-lang.org/u/eike)\
**Post date:** [February 23, 2021, 9:12am UTC](https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988/3 "2021-02-23T09:12:53Z")

</div>

There is no way of enforcing that certain messages only occur while your model is in a certain state. You have two options:

1. You can ignore the wrong messages.
2. You can transition into an error state when you receive a wrong message.

You have to decide and of course, you can mix and match depending on the severity of the wrong state/message combination.

Splitting the app into pages/modules can make this more pleasant because you can filter out the wrong pairs first and then you only have to handle the interesting correct combinations. However, you still have to make the decision somewhere; note that the example in the other post does not compile because the case `(InitialMsg msg, Quiz model)` is not handled, for example.

---

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [February 23, 2021, 9:25am UTC](https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988/4 "2021-02-23T09:25:53Z")

</div>

I tend to set up a default no-op for any (msg, model) combination that is not matched, rather than have an error state. Like this:

```auto
update : Msg -> Model -> (Model, Cmd Msg)
update mg ml =
  case (mg,ml) of
    ...

    (_, _) -> (ml, Cmd.none)

```

The reason being, that due to the synchronous nature of messages, it is possible for multiple message to be in-flight at the same time. For example, suppose a user presses a button very quickly twice in succession. The first click might cause a state change, and the second one might then land in the wrong state. It is usually safe to just no-op anything your state machine does not match, and bear that pattern in mind when designing it.

---

<div class="post-metadata">

**Author:** ![perty](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/perty/32/4459_2.png) [@perty](https://discourse.elm-lang.org/u/perty)\
**Post date:** [February 25, 2021, 7:29am UTC](https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988/5 "2021-02-25T07:29:11Z")

</div>

As a rule of thumb, I avoid using Maybe and underscore in case statements. Sometimes there is no way you can avoid them but I prefer to change my model instead.

Consider remote data, as a typical example. Data that is loaded from the server is either fetched or not fetched yet, right? So our model could have a Maybe for everything that comes from the server. But then you realize that it could also be that the server responded with an error. Not to mention that there is a state where you are waiting for an answer. Fortunately, there is someone who has done something to this. Read [How Elm Slays a UI Antipattern](http://blog.jenkster.com/2016/06/how-elm-slays-a-ui-antipattern.html) on how Elm slays an anti-pattern in UX.

Why not using catch-all in case statements? My main concern in this is that the compiler will not help you if you add a new case that you should handle. You sacrifice a bit of the type-driven development that is such joy when working with languages like Elm.

---

<div class="post-metadata">

**Author:** ![rupert](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/rupert/32/1775_2.png) [@rupert](https://discourse.elm-lang.org/u/rupert)\
**Post date:** [February 25, 2021, 10:51am UTC](https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988/6 "2021-02-25T10:51:23Z")

</div>

> [@perty](#):
>
> My main concern in this is that the compiler will not help you if you add a new case that you should handle.

Yes, it is the disadvantage of this state machine pattern. But there could be a lot of (msg \* state) combinations that are invalid, and it would be inconvenient to have to write them all out.

---

<div class="post-metadata">

**Author:** ![Jess\_Bromley](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/jess_bromley/32/3396_2.png) [@Jess\_Bromley](https://discourse.elm-lang.org/u/Jess_Bromley)\
**Post date:** [March 4, 2021, 11:46am UTC](https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988/7 "2021-03-04T11:46:41Z")

</div>

> [@perty](#):
>
> My main concern in this is that the compiler will not help you if you add a new case that you should handle. You sacrifice a bit of the type-driven development that is such joy when working with languages like Elm.

One solution to this is to split out messages into separate types of messages, _without_ going quite as far as in Lucas\_Payr’s suggestion (I generally try to avoid creating a module with a separate [model, view, update] triad unless absolutely necessary: both because of the overhead and because you often realise later that you didn’t want to compartmentalize the data as much as you originally thought!). So you could do this:

```auto
type Model
    = Initial InitialState
    | Quiz QuizState
    | Complete CompleteState

type Msg
    = InitialEvent InitialMsg
    | QuizEvent QuizMsg
    | CompleteEvent CompleteMsg

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case ( msg, model ) of
        ( InitialEvent iMsg, Initial state ) ->
            initialUpdate iMsg state

        ( QuizEvent qMsg, Quiz state ) ->
            quizUpdate qMsg state

        ( CompleteEvent cMsg, Complete state ) ->
            completeUpdate cMsg state

        _ ->
           ( model, Cmd.none )

initialUpdate : InitialMsg -> InitialState -> ( Model, Cmd Msg )
initialUpdate iMgs state =
    ...

```

Each of of `initialUpdate`, `quizUpdate` and `completeUpdate` will still have a full `case` statement covering every possible _relevant_ message. So now if you add an additional possible message to the QuizMessage type the compiler will still make sure you handle it.

---

<div class="post-metadata">

**Author:** ![DullBananas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dullbananas/32/3709_2.png) [@DullBananas](https://discourse.elm-lang.org/u/DullBananas)\
**Post date:** [March 7, 2021, 8:59pm UTC](https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988/8 "2021-03-07T20:59:16Z")

</div>

> [@perty](#):
>
> My main concern in this is that the compiler will not help you if you add a new case that you should handle.

Ideally you could define Msg as `() -> (Model, Cmd Msg)` to avoid handling invalid Msg+Model combinations, but this is prone to the stale update bug

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [March 17, 2021, 8:59pm UTC](https://discourse.elm-lang.org/t/state-machine-like-model-advice/6988/9 "2021-03-17T20:59:53Z")

</div>

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.
