In Elm, What happens when?

Following on from my question Modifying the string in a text box pushes the cursor to the end and @jessta’s reply: “A Cmd msg returned from update is dispatched immediately before the call to view. In the case of a Cmd msg calling a port, the port will be called synchronously so the view won’t be updated at the time your JS is run.

My question is:
What do we know about the Elm runtime timeline? What happens when?

  1. a message is fired
  2. update captures the message, chooses the appropriate branch, and executes the branch…
  3. at the end of the branch is ( update model, run command )

What can we say about this last part?

Does the instruction to update the model happen BEFORE the command is run?

If so, does it COMPLETE before the command is run?
So that values in the model are ready for use by anything that follows the command.

This is relevant in the case of a command that is pushing something through a port: if the model is updating an element to which the port-JavaScript needs access, then the model update must happen and complete first.

Do we therefore have the following timeline…

model is updated —> command is run —> (port JS is executed) —> view is updated

…reliably in that order?

@jessta continues, “The general solution to this is to delay the JS from running until the next animation frame which will be after the view has been updated. You can do this in JS with setTimeout() or with requestAnimationFrame() called within the function you’ve subscribed to the port on the JS side.” And this is backed up by other replies which say to use requestAnimationFrame.

Is this the way to ensure the view is updated before port JS is run, ie:

model is updated —> command is run —> (port JS is delayed til next frame) —> view is updated —> port JS is executed

Just for completeness, can we switch the end of an update branch around, so that the command runs and port JS is executed BEFORE the model is updated?

Finally, are there any other timing ‘issues’ an Elm programmer should be aware of?

3 Likes

Neither, both the new model value and the command to be run is returned to the runtime. The runtime runs the commands (some can be asynchronous and so won’t complete until later, some are synchronous, like ports, and run immediately) and then the run time calls the view function with the new model value.
You can’t observe any of this ordering from with in Elm. Any DOM interacting commands provided by Elm always include a delay so they run on the same view as the model value they were returned with.

The ordering isn’t relevant to Elm programmers writing Elm as there isn’t a way to observe an of this ordering from within an Elm program, But it is relevant to people writing JS programs that interact with the same global state that an Elm program interacts with.

This timing is implementation dependent and has changed in various versions of Elm. If you’ve got JS code that has any particular expectations of timing it will likely need to be reviewed and tested when upgrading between Elm versions.

An example of a change in the implementation that changed timing expectations in JS code called from a a port is the change in Elm 0.19 that made sending to ports synchronous because browsers expect various APIs to only be called from within an evet handler from a user event. eg. you can only open popup windows in response to a user click.

2 Likes

Hi @jessta, many thanks for your detailed reply.

both the new model value and the command to be run is returned to the runtime.

But once the new model and the command are returned to the runtime, what happens then? Ultimately it’s all running on JavaScript which, being single threaded, must either do the model bit first or the command bit first. No?

The runtime runs the commands (some can be asynchronous and so won’t complete until later, some are synchronous, like ports, and run immediately) and then the run time calls the view function with the new model value.

So we can confidently say…

a) the model is updated —> the view is updated
and
b) the command is run —> (port JS is executed)

…but we know nothing about the order in which a) and b) begin or complete?
Or does your “and then” mean that b) happens before a) ?

You can’t observe any of this ordering from with in Elm. Any DOM interacting commands provided by Elm always include a delay so they run on the same view as the model value they were returned with.

                      |- the command 
                      V
( { model | ... }, Cmd msg )  
          ^  
          |- the model value that the command was returned with  

So are you saying that:
If the command interacts with the DOM,
it is delayed, until the view has updated according to the new model value?

So why do we need to “delay the JS from running until the next animation frame” ?
Or, when you say, “always include a delay”, do you mean that a delay must always be added to the JS code by the person writing it?

If so, do we know anything about how long the delay should be?
Is ‘up to the next animation frame’ always enough (in 0.19)?

Sorry to be obtuse. Once I get my head around this (and tasks) I’ll be cookin’ on gas :slight_smile:

Without considering the view, I would say it goes like this:

  1. At the end of your update you make a new Model. That immediately becomes the new model as recognized by the runtime.

  2. Any output Cmd is processed by an Elm kernel module to produce some side effect. Some Cmds are fire and forget, and produce no Msg. Some do require you to define a function to build a Msg when constructing the Cmd so that they can give a response.

  3. The response Msg and new Model are again passed to your update function.

It does not actually matter if the Model becomes the new one immediately after update - It could also be set as the new one later on, and it would make no difference.

The reason it does not matter is that all the Cmds you build to run side effects are given all their parameters up-front. They do not even refer to your Model at all. The Cmds are all self-contained things describing desired side-effects.

You could pass your entire Model to a port, if you so desired. In which case, the one passed to the port will be the one you gave to the Cmd constructor used to invoke the port. (This could actually differ to the Model returned from your update function.)

1 Like

Hi @rupert, thank you.

There seem to be two relevant situations, and you’ve neatly covered the other one — the flip side, if you like — that any command that does not affect the view/DOM does not require the view (and therefore the model) to be updated before the command (or it’s consequences) are executed. Because any values it needs can be packaged up with it from the previous model or from what just arrived in update:

data which came in with the update message    
                                 \  
( { model | ... }, command <command data> )  
                                 /  
data which is already in the model from before (model.data. ... )  

The last part of your reply is particularly interesting. That we could have:

( { model | a = x }, command { model | a = y } )  
     /                          /  
old model being         old model being
updated which will      updated to be sent  
become the new model    with the command

Not necessarily a good idea, but illustrative of the independence of the model part and the command part.

[I’m drawing it out here for the benefit of anyone who’s even more of a newbie than me :wink: — hope I’ve got it right.]

Elm values are immutable so you can’t get or pass a mutable reference to a value. This means that the only values a function can access are those that are passed to it. 'When does the model get updated?" isn’t a meaningful question. All of the callback functions (view, subscriptions, update etc.) the runtime calls after a call to update will be passed the model value that update returned.

Just for clarity. All Cmds are started straight after update returns. The delay for DOM interacting Cmds isn’t a feature of the runtime, it’s a feature of the particular Cmds that are provided to Elm by the core packages.
eg. https://package.elm-lang.org/packages/elm/browser/latest/Browser-Dom#focus is written to set focus on an element after the next call to view.

With ports you need to write that delay in yourself.

1 Like

Thank you, @jessta

Part two of your reply is indeed clear.

And I think I understand part one:-

Elm values are immutable so you can’t get or pass a mutable reference to a value.
This means that the only values a function can access are those that are passed to it.
'When does the model get updated?" isn’t a meaningful question.

So a function gets passed a copy of the model, it then generates another copy (with perhaps modified values) which it passes to the next function. A chain of functions passing immutable versions of the model.

So there is no central mutable copy of the model that ultimately gets updated at the end of each cycle of update. The model is just a succession of immutable copies passed from function to function.

All of the callback functions ( view , subscriptions , update etc.) the runtime calls after a call to update will be passed the model value that update returned.

That makes sense. And is very reassuring.
So everything that follows ( { model | value = newValue }, ...) at the end of a branch in update receives a copy of this updated model.

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