Messages purpose

Why does TEA use separate msg type and uptate function? Why not just pass model -> (model, Cmd) functions to Html

3 Likes

I think the main reason is that you can reasonably log a message type but not a function

1 Like

Msgs are the way an Elm program communicates with the world outside Elm. Msgs come from user interactions events, such as clicks on html elements, but they also come from IO events like http requests and ports.
update is a central place to interpret all incoming msgs in to changes to the model.

Elm could have picked an architecture with lots of call back functions being passed to the runtime for it to call but having a central place to deal with things that can change the model makes it easier to see how and why the model can change.

A model -> (model, Cmd) passed to an onClick makes it tricky to figure out exactly what kind of things can change the model, that Cmd would need to include it’s own callback to update the model in someway too, which might also return other Cmd that would have it’s own call backs.
While this can work (clearly, it’s how most JS apps work), it does make it harder to find which code interacts with the model.

Having a single callback function for all events mean that you have a single tree of functions to look at when evaluating how and why the model can change.

5 Likes

And actually, I realized that we can structure our elm programs the way you’re pondering if we really want to: example

1 Like

You might be able to do this now but this makes the debugger less helpful (messages become unreadable in the debugger).

Evan recommended against it (this was how the old elm-mdc used to be implemented) and I would not be surprised if future versions of Elm would make this invalid.

1 Like

Roman Potasovs does that in his talk below (or a very similar pattern, I haven’t seen it in a while).

The primary benefit he gets from that IIRC is not to have to rerender the view when your handler doesn’t actually change the value of the Model.

I agree with the others above that this is probably not what you’re looking in a regular app, because debugging will be very hard. You won’t be able to use the Elm debugger (since you’ll just see the whole Model, with few hints as to what changed), nor will you be able to Debug.log the msg that just occurred since you won’t have any message really.

You will also spread the knowledge of how to update your Model, from the one update function in your code to all the view and subscriptions code in your application.

Having a single Msg type is great for listing what interactions are possible in the project, which has a number of benefits. Spreading all of that into view and subscriptions makes it very hard to know what can happen and can’t. That will also move a lot of complexity to those functions (if you find your update handlers complex, imagine adding them to your view/subs), and my gut feeling is that it will create more code duplication as well.

It is possible to do it this way, but I find that there are lot of tradeoffs to this, which are not worth it, or maybe in very select and narrow pieces of an application that are either very simple or absolutely require the performance boost because you care about frames per second (which you should reach out for after noticing the problem IMO).

5 Likes
view : Model -> Html Msg
view model =
    div []
        [ button [ onClick (\msgModel -> { msgModel | count = model.count + 1 }) ] [ text "+1" ]
        , div [] [ text <| String.fromInt model.count ]
        , button [ onClick (\msgModel -> { msgModel | count = model.count - 1 }) ] [ text "-1" ]
        ]

There is a problem with this - in the update lambdas, you have 2 models to choose from: model and msgModel, and you incremented the count from model. This could lead to the stale update problem:

https://ellie-app.com/PQPXs4k2x9a1

Of course, you can fix this by always using msgModel - but I think this style makes this mistake more likely.

1 Like

I don’t think there is the stall problem if you do like this:

view : Model -> Html Msg
view model =
    div []
        [ button [ onClick (\msgModel -> { msgModel | count = msgModel.count + 1 }) ] [ text "+1" ]
        , div [] [ text <| String.fromInt model.count ]
        , button [ onClick (\msgModel -> { msgModel | count = msgModel.count - 1 }) ] [ text "-1" ]
        ]
2 Likes

On a more fundamental level, you can think of the various ways Elm allows you to construct program types as a hierarchy. The most fundemental type in this hierarchy is Platform.worker:

worker :
    { init : flags -> ( model, Cmd msg )
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    }
    -> Program flags model msg

As you see, this Program type allows for I/O with the external world through flags, Cmd, and Sub. Both of latter these generate messages and are handled by the update function.

Next is Browser.element (and Browser.document which is super similar):

element :
    { init : flags -> ( model, Cmd msg )
    , view : model -> Html msg
    , update : msg -> model -> ( model, Cmd msg )
    , subscriptions : model -> Sub msg
    }
    -> Program flags model msg

If you assume a few ports, you can easily implement this with a bit of JS on top of Platform.worker. Browser.sandox can then easily be implemented on top of Browser.element, as it is just a straightforward simplification. Finally, Browser.application can be implemented with another pair of ports to handle navigation.

The point is, that rendering Html could have equally been implemented as just another render : Html msg -> Cmd msg and the update function with msg would be all that was left. So it is a more fundamental part of Elm’s model than view functions (and indeed if Elm was used in other contexts, there would be no view function).

2 Likes

Cutting msg out, is similar to how the folks of Hyperapp have decided to do it. One plus you get from that, is a “free” splitting of your update into smaller functions, since you pretty much don’t have one anymore.

3 Likes

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