Platform.Cmd.batch executes commands in reverse order

777 views
Skip to first unread message

Frank Bonetti

unread,
May 12, 2017, 11:36:39 AM5/12/17
to Elm Discuss
If you batch a list of commands, they will be executed in reverse order. For example:

Cmd.batch
   
[ Task.perform (always (AddString "first")) (Task.succeed ())
   
, Task.perform (always (AddString "second")) (Task.succeed ())
   
, Task.perform (always (AddString "third")) (Task.succeed ())
   
]

Will print:

third
second
first


Has anyone else noticed this behavior? If so, do you know why Cmd.batch was implemented this way?

Here's a demo:

Peter Damoc

unread,
May 12, 2017, 11:53:19 AM5/12/17
to Elm Discuss
The execution of a Cmd.batch list of commands has not ordering guarantee. 
What you see is an artifact and you should not treat it as a predictable way of execution. 
The way to think about a Cmd.batch is that they will get executed and you will eventually get a reply. 

If you need a list of tasks executed in order, you need to implement what you mean by that yourself. 
For example, Task.sequence executes all tasks in order and if one fails, the entire execution fails. 
Task.andThen is another way to chain execution where you have access to the result of the previous execution. 
It depends on what you want to happen. 



--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
There is NO FATE, we are the creators.
blog: http://damoc.ro/

Peter Damoc

unread,
May 12, 2017, 12:16:14 PM5/12/17
to Elm Discuss
Here are two approaches to just adding the texts (I'm assuming some interface where you want to send a series of messages one after the other and having the user seeing them arrive one after the other with some delay between them) 



On Fri, May 12, 2017 at 6:36 PM, Frank Bonetti <frank.r...@gmail.com> wrote:

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Frank Bonetti

unread,
May 12, 2017, 12:17:40 PM5/12/17
to Elm Discuss
> The execution of a Cmd.batch list of commands has not ordering guarantee. 

Do you know that for sure? There's no documentation for Platform.Cmd.batch, so the official stance is unclear.

I want to execute these tasks independently, concurrently, in the order they're provided, and for each one to update the model immediately after completion. If one of them fails, the others should still execute. Chaining theses tasks together with Task.map3, Task.andThen or Task.sequence won't work for this use case.

Cmd.batch works perfectly for this use case; I just find it strange that it executes the list in reverse order and was wondering if there was a reason behind it.



On Friday, May 12, 2017 at 10:53:19 AM UTC-5, Peter Damoc wrote:
The execution of a Cmd.batch list of commands has not ordering guarantee. 
What you see is an artifact and you should not treat it as a predictable way of execution. 
The way to think about a Cmd.batch is that they will get executed and you will eventually get a reply. 

If you need a list of tasks executed in order, you need to implement what you mean by that yourself. 
For example, Task.sequence executes all tasks in order and if one fails, the entire execution fails. 
Task.andThen is another way to chain execution where you have access to the result of the previous execution. 
It depends on what you want to happen. 


On Fri, May 12, 2017 at 6:36 PM, Frank Bonetti <frank.r...@gmail.com> wrote:
If you batch a list of commands, they will be executed in reverse order. For example:

Cmd.batch
   
[ Task.perform (always (AddString "first")) (Task.succeed ())
   
, Task.perform (always (AddString "second")) (Task.succeed ())
   
, Task.perform (always (AddString "third")) (Task.succeed ())
   
]

Will print:

third
second
first


Has anyone else noticed this behavior? If so, do you know why Cmd.batch was implemented this way?

Here's a demo:

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Frank Bonetti

unread,
May 12, 2017, 12:22:34 PM5/12/17
to Elm Discuss
Thanks for you input! I like the second approach. But just so that I don't waste your time, I want to be clear that I'm not looking for a solution. I asked this question purely out of curiosity.



On Friday, May 12, 2017 at 11:16:14 AM UTC-5, Peter Damoc wrote:
Here are two approaches to just adding the texts (I'm assuming some interface where you want to send a series of messages one after the other and having the user seeing them arrive one after the other with some delay between them) 


On Fri, May 12, 2017 at 6:36 PM, Frank Bonetti <frank.r...@gmail.com> wrote:
If you batch a list of commands, they will be executed in reverse order. For example:

Cmd.batch
   
[ Task.perform (always (AddString "first")) (Task.succeed ())
   
, Task.perform (always (AddString "second")) (Task.succeed ())
   
, Task.perform (always (AddString "third")) (Task.succeed ())
   
]

Will print:

third
second
first


Has anyone else noticed this behavior? If so, do you know why Cmd.batch was implemented this way?

Here's a demo:

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Frank Bonetti

unread,
May 12, 2017, 12:33:15 PM5/12/17
to Elm Discuss
Also, to the best of my knowledge, the only way to execute Tasks concurrently and independently is to use Cmd.batch. The ideas you provided execute the tasks one after another. Task.map3 is probably the closest to what I'm looking for, but it has to wait for all of the tasks to either fail or succeed.



On Friday, May 12, 2017 at 11:16:14 AM UTC-5, Peter Damoc wrote:
Here are two approaches to just adding the texts (I'm assuming some interface where you want to send a series of messages one after the other and having the user seeing them arrive one after the other with some delay between them) 


On Fri, May 12, 2017 at 6:36 PM, Frank Bonetti <frank.r...@gmail.com> wrote:
If you batch a list of commands, they will be executed in reverse order. For example:

Cmd.batch
   
[ Task.perform (always (AddString "first")) (Task.succeed ())
   
, Task.perform (always (AddString "second")) (Task.succeed ())
   
, Task.perform (always (AddString "third")) (Task.succeed ())
   
]

Will print:

third
second
first


Has anyone else noticed this behavior? If so, do you know why Cmd.batch was implemented this way?

Here's a demo:

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Peter Damoc

unread,
May 12, 2017, 12:46:29 PM5/12/17
to Elm Discuss
On Fri, May 12, 2017 at 7:33 PM, Frank Bonetti <frank.r...@gmail.com> wrote:
Also, to the best of my knowledge, the only way to execute Tasks concurrently and independently is to use Cmd.batch.

Yes. But you cannot have concurrency and guaranteed ordering in the same time without some kind of synchronization mechanism.

Imagine for a second that the first task takes 2 seconds the second task 1 second and the third 500ms. They finish in reverse order. If you want their result immediately after they finish, the results will come in the wrong order.

Also, regarding not being guarantees for order here is an older issue in the core where this is mentioned:
https://github.com/elm-lang/core/issues/730#issuecomment-253584490


Max Goldstein

unread,
May 12, 2017, 12:46:53 PM5/12/17
to Elm Discuss
"independently, concurrently, in the order they're provided"

This is a contradiction. Even if the runtime started the tasks at the same time, the works they do can complete in any order. Either your code (Elm or otherwise) should work with any ordering, or you should sequence things in Elm tasks.

If the runtime is iterating through the list backwards, that's an implementation detail.

Frank Bonetti

unread,
May 12, 2017, 1:03:24 PM5/12/17
to Elm Discuss
> "independently, concurrently, in the order they're provided"

This is a contradiction. Even if the runtime started the tasks at the same time, the works they do can complete in any order. Either your code (Elm or otherwise) should work with any ordering, or you should sequence things in Elm tasks.


I'm well aware that the tasks can complete in an order. I was just wondering why the commands are started in reverse order.



> If the runtime is iterating through the list backwards, that's an implementation detail.

Is it just an implementation detail? If the documentation clearly stated that you should not expect the commands to be started in any particular order, sure, this would be an implementation detail, but as it stands this behavior is "undefined".

It's kind of like how Set orders the elements it's given.
Set.fromList [4,2,5,3,1] |> Set.toList == [1,2,3,4,5]

This happens because Set is implemented as a Dict, and Dict is implemented as a binary tree. You might call this an implementation detail, but I wouldn't because it affects what the function returns. A true implementation detail doesn't affect the output.

Frank Bonetti

unread,
May 12, 2017, 1:07:57 PM5/12/17
to Elm Discuss
> Imagine for a second that the first task takes 2 seconds the second task 1 second and the third 500ms. They finish in reverse order. If you want their result immediately after they finish, the results will come in the wrong order.

Totally understand. Why do the commands start in reverse order though?


> Also, regarding not being guarantees for order here is an older issue in the core where this is mentioned:
https://github.com/elm-lang/core/issues/730#issuecomment-253584490

Ok, so it sounds like this is just undefined behavior. Fair enough.

Peter Damoc

unread,
May 12, 2017, 1:45:55 PM5/12/17
to Elm Discuss
On Fri, May 12, 2017 at 8:07 PM, Frank Bonetti <frank.r...@gmail.com> wrote:
> Imagine for a second that the first task takes 2 seconds the second task 1 second and the third 500ms. They finish in reverse order. If you want their result immediately after they finish, the results will come in the wrong order.

Totally understand. Why do the commands start in reverse order though?

What you are seeing is an artifact of the current implementation. 
In short it happens because of the way lists are implemented and because it takes the first element from a list (car) and cons it to another list 
The simplified version of what happens is exemplified by this ellie example:
https://ellie-app.com/39Qy5qycs8Qa1/0

Now, if you want to go further and ask another why, you will have to get very intimate with the rest of the implementation details in order to understand the trade-offs in various approaches and to understand why this one has been chosen. (I can only guess that it was a performance reason) 

If you have enough JS knowledge you can dig deeper into the Platform/Scheduler code.  
Elm's JS kernel code is quite readable. 


Frank Bonetti

unread,
May 12, 2017, 1:49:09 PM5/12/17
to Elm Discuss
To give you some context, I'm working on a single page app:

1. When the user loads a particular page, the app makes 10 independent ajax requests to get various pieces of data. Some of these requests take a while to complete while others complete instantaneously.
2. In general, it doesn't matter when these requests start and end, but there is one particular request which needs to be executed as quickly as possible in order to make the page feel snappy.
3. Since the browser can only execute a limited number of ajax requests in parallel, the order of when the requests start is critical. If my most important request is put last in the queue, it won't be executed until the AJAX thread pool frees up a spot, which could take a while, resulting in a page that feels slow.

With that in mind, and thinking that Cmd.batch would execute my commands in the order given, I structured my Cmd.batch like this:

cmd =
   
Cmd.batch
       
[ getReallyImportantPieceOfData
       
, getLessImportantThing1
       
, getLessImportantThing2
       
, getLessImportantThing3
       
, getLessImportantThing4
       
, getLessImportantThing5
       
, getLessImportantThing6
       
, getLessImportantThing7
       
, getLessImportantThing8
       
, getLessImportantThing9
       
]

The page felt slow, so I opened up Chrome's network tab and found this:

/getLessImportantThing9.json
/
getLessImportantThing8.json
/getLessImportantThing7.json
/getLessImportantThing6.json
/getLessImportantThing5.json
/getLessImportantThing4.json
/getLessImportantThing3.json
/getLessImportantThing2.json
/getLessImportantThing1.json
/getReallyImportantPieceOfData.json

My most important request was placed at the back of the queue! Because the browser can only execute 5 or 6 ajax requests in parallel, it ended up taking a considerable amount of time for my most important request to be executed. The solution was simple - I just reversed the list before sending it to Cmd.batch - but it left me wondering why Cmd.batch was implemented this way.

Frank Bonetti

unread,
May 12, 2017, 1:52:27 PM5/12/17
to Elm Discuss
> In short it happens because of the way lists are implemented and because it takes the first element from a list (car) and cons it to another list 

Makes sense. That's what I assumed was happening, but wasn't sure. Thanks for the help!

Peter Damoc

unread,
May 12, 2017, 2:03:25 PM5/12/17
to Elm Discuss
On Fri, May 12, 2017 at 8:49 PM, Frank Bonetti <frank.r...@gmail.com> wrote:
To give you some context, I'm working on a single page app:

1. When the user loads a particular page, the app makes 10 independent ajax requests to get various pieces of data. Some of these requests take a while to complete while others complete instantaneously.
2. In general, it doesn't matter when these requests start and end, but there is one particular request which needs to be executed as quickly as possible in order to make the page feel snappy.
3. Since the browser can only execute a limited number of ajax requests in parallel, the order of when the requests start is critical. If my most important request is put last in the queue, it won't be executed until the AJAX thread pool frees up a spot, which could take a while, resulting in a page that feels slow.

This is important information. 

I don't know what the solution could be but this sounds like a scenario to be aware of.

Maybe make an issue on the core where to describe the interaction between Cmd.batch and the limit of requests a browser can make concurrently. 


Rupert Smith

unread,
May 12, 2017, 5:50:38 PM5/12/17
to Elm Discuss
On Friday, May 12, 2017 at 4:53:19 PM UTC+1, Peter Damoc wrote:
The execution of a Cmd.batch list of commands has not ordering guarantee. 

I suspect that whilst there is no ordering guarantee given, the actual implementation probably does a 'foldr' executing them in reverse order. This behavior seems very consistent.

In theory they could be executed in parallel and non-deterministically. In reality its javascript on the browser, which is single threaded, so there is no concurrency.

Mark Hamburg

unread,
May 13, 2017, 6:32:59 PM5/13/17
to Elm Discuss
What you can do is emit the commands in two batches. The first sends the time critical operation(s) and sends a trigger message back to send the second batch of commands. Note that you can use Cmd.batch to send multiple commands on the delayed path:

type Msg =
    Later (Cmd Msg)
    
update : Msg -> model -> ( model, Cmd Msg )
update msg model =
    case msg of
        Later cmd -> ( model, cmd )

later : Cmd Msg -> Cmd Msg
later cmd =
    Task.succeed cmd
        |> Task.perform Later

Mark

Reply all
Reply to author
Forward
0 new messages