Producing side effects directly from view functions?

I’d like to clarify my understanding on why views cannot produce side-effects directly.

Here is an example where I have a simple view with a button. When the user clicks the button I’d like to produce a random value (a side-effect), but in order to do so I need to produce a Cmd Msg which can only be done from the update function. So another distinct Msg is needed to tell the update function to produce the Cmd.

I understand why this is strictly from a function annotation perspective. You need a Cmd to produce a side-effect. Randomness (with Random.generate) is a side-effect and the view function’s annotation doesn’t work with Cmds.

I can’t say I understand it from a TEA perspective yet and that I’m missing something obvious. Shouldn’t event handlers in the view be able to produce side-effects directly?

4 Likes

So, as it stands, the only way to execute side-effects if with a Cmd msg. This means I can look at any piece of code and get a sense of where side-effects are happening very easily: just look for Cmd msg and I’m good to go.

While I can’t think of a technical reason why Html msg wouldn’t be able to produce side-effects in response to events, I do feel like it would be an extra complication for the user. Where are side-effects produced? Well, look for Cmd msg and also read all the view code because some event somewhere may produce a side-effect, somewhere.

I fear it would also encourage moving your business logic into your views. I like that the current model is “it’s just a pure function that describes how to render your app given the current state”. update, then, is the single place where you decide what to do next, based on the current state (the model) and information on what just happened (the message).

That said, generating randomness doesn’t require side-effects in Elm, as long as you keep track of the seed. So creating a random int when you click a button is already possible: https://ellie-app.com/bGgCF2p6ka1/0

3 Likes

So while it’s true that the majority of apps and recommendations focus on using a message to tell your app what to do next, be it update the model or the command itself, it is possible to create an exectuor constructor for your Msg that will simply just run the commands you give it. For example:

type Msg = Command (Cmd Msg)

update : Msg -> Model -> (Model, Cmd Msg)
update msg model = 

   case msg of 
      Command cmd -> (model, cmd)

You can see how this would look as a working example here: https://ellie-app.com/embed/nBXyMcVVFa1/0. I’ve seen at least 2 people use this in production, the argument being that seeing directly which side-effects get triggered by which views allow the reader to better identify what happens. One of the major downside of this approach is that your view code suddenly contains code you’d expect to only find in the update function. I’d recommend keeping the centralized effects approach, instead.

2 Likes

One concrete practical reason they shouldn’t is requestAnimationFrame batching.

Right now, if I subscribe to mousemove, I’ll get flooded with events when the user moves the mouse. Each time an event comes in, update runs, potentially changes Model, and potentially runs more effects.

However, regardless of how quickly events are coming in, the Elm Runtime still only calls view once per animation frame - with whatever the current Model happens to be at that moment. It can do this optimization safely because view only returns Html and performs no side effects. Since view cannot possibly do anything other than describe how the page ought to look, it’s always safe to skip running it except when it’s potentially time to change how the page looks.

Now suppose view had the type Model -> ( Html Msg, Cmd Msg ) instead.

In this world,it’s no longer safe to skip calling view under any circumstances. If we get flooded with events, we have to call both update and view on every single event, no matter what. After all, what if view was going to produce an effect on the 14,203rd invocation? There’s no way to know without running it every single time.

That’s is a hefty performance price to pay for being able to have both view and update describe effects directly. :sweat_smile:

I’d say there are other design principle reasons to keep them separated, but this is one practical reason. :slight_smile:

11 Likes

I should clarify my phrasing.

My title was poorly worded. I don’t mean to say that invoking a view function should produce a side effect. That’d be just as strange as invoking a view function that returns a Msg.

Rather, I’m wondering about something like view : Model -> Html (Cmd Msg).

Perhaps not exactly that, but something like that demonstrates what I’m thinking. It’s a view function that doesn’t return side effects but it is a function that returns Html that is capable of producing side effects.

I updated the example to make explicit what I’m talking about. It doesn’t compile and there are nits to pick, but the changes to update and view demonstrate my question. It’s similar to what @eeue56 suggested but with less boilerplate.

1 Like

Relevant code to make it easier to find…

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        RandomResult suit ->
            Just suit ! []

-- ...

view : Model -> ???
view model =
    div
        []
        [ button
            [ onClick clickCmd ]
            [ text "Random Suit" ]
        , suitView model
        ]


clickCmd : Cmd Msg
clickCmd =
    Random.generate RandomResult suitGenerator

You can do view : Model -> Html (msg, Cmd msg), but that uses more boilerplate. Here’s a question for you: what about the cases where you need to update the model during the firing of a command? For example, if you have a form and the command in question is to store that data in a server and wait for the response. How would you want that to look?

Note that I’m not suggesting that update’s annotation should change. I’m not really suggesting anything. I’m only trying to better my understanding on why all side effects must ultimately go through update even though there are some side effects that come from Html event handlers and don’t make any changes to the model.

Imagine you have the following flows:

Msg update filter emitted Cmd Msg
Click Submit, Keyboard Cmd+Enter is the submitted data valid? (model, postNewFormData)
Click X, Keyboard Esc is the modal open? ({m | modalState = Closed}, Cmd.none)

These are some pretty common situations! And in these, if you have your Cmd Msg emitted from view

  1. you have to handle validation and postNewFormData in every event handler instead of once in update.
  2. you can’t update the model/modal without changing to view : Model -> Html (Model, Cmd Msg).

I think that #2 illustrates why we don’t do this best: because it is not the view’s responsibility! Once it’s both update and view, it mixes event handling and presentation. These are separate domains, why shouldn’t they be separate functions?

I’ve been told that certain cljs frameworks have done this, and that it has been their fatal flaw. Once you couple these two domains, you start expecting the data to be shaped the same as your view. This makes refactoring a whole lot harder than it would have to be!

1 Like

I interpreted the question as what if Html msg were more like Cmd msg.

In other words, what if there were a function like this: on : String -> Decoder (Cmd msg) -> Html.Attribute msg.

A practical issue with that approach is that commands are asynchronous. If a command (say, an HTTP request) takes a while, what should happen to events that arrive in the meantime? If we let them through, that means the order of actions by the user doesn’t correspond to the order in which messages in update arrive, which could potentially be very confusing.

My previous comments about needing to actually read the view code to figure out where side-effects happen still stand - assuming the signature wouldn’t change, I’d presumably end up with a mix of “pure” events and “side-effectful” events.

So, my opinion: Html msg synchronously bubbles events to update because it’s the least confusing thing to do and there are no obvious advantages to doing it differently.

2 Likes

This helped me best to understand the problem with the example use case given:

When the user clicks the button I’d like to produce a random value (a side-effect).

Looking at this use case, it was easy for me to forget that API-wise, Random.generate is just as asynchronous (and can take just as long) as communication with a remote system.

When I think of how long it could take to get a result, it makes sense. I would not want to leave the user wondering, has my request registered by the system? I would want to update the UI in the meantime to inform the user that the system is working on it.

@brian I definitely agree that if the model needs to be updated at the same time that a Cmd Msg is produced then the current architecture supports that well.

These responses are helping me understand what my question really is and it’s more of an observation. If in response to an Html event a side-effect needs to be produced and the model does not need to be updated, then the current architecture requires a bit more boilerplate.

@ilias This is spot on to what I was thinking. Html events very often produce side-effects and the model may or may not need to be updated when that side effect is produced.

I’m not sure I understand why Cmds being asynchronous is an issue. Msgs are asynchronous too.

While thinking about this something else occurred to me. Sometimes a Cmd needs to know about the current model to know what side effect it should produce. That would require a change to on and others.

on : String -> (model -> Decoder (Cmd msg)) -> Html.Attribute msg

I’m still wondering if there’s a more elegant solution at the level of the Elm architecture. Something I’ll ponder on for a while…

Messages produced by HTML events arrive in the exact order as the events happened. Commands have no ordering guarantees, but html-events do. I’m not sure how big of an issue this would be in practice.

I’ve been trying to think of cases where the model wouldn’t be updated (and hence, this discussion makes sense). It seems that for most actions, you want to inform the user somehow (e.g. disable a form while it’s submitting, show a “loading” state while loading data, …). The most notable exception that comes to mind that makes sense as a direct result from user-interaction is navigation. For hash-navigation, we can and do already use a direct side-effect: the URL changes, which triggers the subscription, which informs update.

I think that’s the crux of my argument: whether an HTML event should change the model, trigger a side-effect to happen or both isn’t for my view to decide. The mental model that events product messages that inform you that something happened so you can respond to it based on the current state is a very simple one.

In my experience, having a central “hub” where messages are mapped to actions (producing new state and/or producing commands for the runtime) is a great for maintainability: I can read update and get a sense of what’s going on.

I’m not sure why that would be the case - a view already has the model (or the relevant part for that view), so couldn’t it apply that model -> Decoder (Cmd msg) function to the model and pass the Decoder (Cmd msg) to this imaginary on?

1 Like

Two side effects without model changes that come to mind is randomness using Random.generate or using a port “synchronously”. The original post has an example for randomness. An example using a port might be clicking a button to plot a point on a third-party mapping library like CesiumJS or Leaflet. In either case you wouldn’t want to update the model until that side-effect was performed successfully.

As @rtfeldman pointed out the view is only called once per animation frame. Additionally, that might get hokey when used with Html.Lazy. The point is the view isn’t necessarily guaranteed to have the latest model in my understanding.

Agreed. That’s why I’m wondering if there’s a more elegant solution (rethinking how side effects work in TEA rather than duct taping a modification to it).

This is crucial! If side effects are produced in the view, you no longer know where all the side effects live. I think that’s the core answer to your “why?”, @charliek. This is a case where the benefits of removing “boilerplate” are not worth the drawbacks.

Also, in my experience it is good to separate your system’s events from the user’s intent. So this:

snippet : Html Msg
snippet =
    button [ onClick ShuffleCards ] [ text "Shuffle!" ]

is way better than this:

snippet : Model -> Html Msg
snippet model =
    button [ onClick (SetOrder model.nextCardOrder) ] [ text "Shuffle!" ]

In short, I guess I don’t get your motivation! Is there some concrete thing you’re trying to do which is really painful because of the way view is set up now?

1 Like

Nope! I started with this example and wasn’t clear on why I needed to produce a Msg to produce a side effect which produces a final Msg. Only trying to better understand why that is necessary.

Here, I have a version of your Elm app that doesn’t produce any side-effects: https://ellie-app.com/qr6W23TBka1/1

The changes that I made include:

  1. Add an integer flag to initialize the model with a random seed
  2. Add a random integer generator in the JS side (the only side effect is outside the Elm app)
  3. Use Random.step to produce a random result and the next seed in a completely pure fashion

If you notice the update function always returns a Cmd.none with the updated model. If it wasn’t for the flags, this could be converted into a Html.beginnerProgram. Hope this helps you out.

1 Like

No. You don’t want to think about when and how often view is called. This is why we have update function and Msg type. It helps to keep even handles in view lazy so there is no construction of extra values during every render to vdom.

In lazy language like haskell it might make sense to abandon update and Msg comnpletely and do implement these things in view. There is still a concern of interleaving of course but update function doesn’t solve that one either. Shpadoinkle is GHCjs framework that explores “update less” / “effectful view” pattern. In strict language you can do this as well but for sure there is some pressure on programmer to make sure stuff doesn’t evaluate all the time.

I think the use case is not the one the OP had in mind, but be aware that it is possible to run the update function immediately in response to an event on the view - which sounds a lot like the view being able to side-effect directly, depending on what we understand by that statement.

See notes 2 and 3 here for an explanation:

https://package.elm-lang.org/packages/elm/virtual-dom/latest/VirtualDom#Handler