Discussion:
[elm-discuss] Platform.Cmd.batch executes commands in reverse order
Frank Bonetti
2017-05-12 15:36:38 UTC
Permalink
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:
https://ellie-app.com/39NxYQfDCHMa1/0
--
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
2017-05-12 15:53:12 UTC
Permalink
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.
Post by Frank Bonetti
If you batch a list of commands, they will be executed in reverse order.
Cmd.batch
[ Task.perform (always (AddString "first")) (Task.succeed ())
, Task.perform (always (AddString "second")) (Task.succeed ())
, Task.perform (always (AddString "third")) (Task.succeed ())
]
third
second
first
Has anyone else noticed this behavior? If so, do you know why Cmd.batch
was implemented this way?
https://ellie-app.com/39NxYQfDCHMa1/0
--
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
For more options, visit https://groups.google.com/d/optout.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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
2017-05-12 16:17:39 UTC
Permalink
Post by Peter Damoc
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
<http://package.elm-lang.org/packages/elm-lang/core/5.1.1/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.
Post by Peter Damoc
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.
Post by Frank Bonetti
If you batch a list of commands, they will be executed in reverse order.
Cmd.batch
[ Task.perform (always (AddString "first")) (Task.succeed ())
, Task.perform (always (AddString "second")) (Task.succeed ())
, Task.perform (always (AddString "third")) (Task.succeed ())
]
third
second
first
Has anyone else noticed this behavior? If so, do you know why Cmd.batch
was implemented this way?
https://ellie-app.com/39NxYQfDCHMa1/0
--
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
For more options, visit https://groups.google.com/d/optout.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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.
'Rupert Smith' via Elm Discuss
2017-05-12 21:50:38 UTC
Permalink
Post by Peter Damoc
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.
--
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.
Mark Hamburg
2017-05-13 22:32:54 UTC
Permalink
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

On Fri, May 12, 2017 at 2:50 PM, 'Rupert Smith' via Elm Discuss <
Post by 'Rupert Smith' via Elm Discuss
Post by Peter Damoc
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.
--
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
For more options, visit https://groups.google.com/d/optout.
--
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
2017-05-12 16:16:07 UTC
Permalink
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)

with subscriptions:
https://ellie-app.com/39PpTzWp5y3a1/0

without subscriptions:
https://ellie-app.com/39PpTzWp5y3a1/1
Post by Frank Bonetti
If you batch a list of commands, they will be executed in reverse order.
Cmd.batch
[ Task.perform (always (AddString "first")) (Task.succeed ())
, Task.perform (always (AddString "second")) (Task.succeed ())
, Task.perform (always (AddString "third")) (Task.succeed ())
]
third
second
first
Has anyone else noticed this behavior? If so, do you know why Cmd.batch
was implemented this way?
https://ellie-app.com/39NxYQfDCHMa1/0
--
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
For more options, visit https://groups.google.com/d/optout.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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
2017-05-12 16:22:34 UTC
Permalink
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.
Post by Peter Damoc
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)
https://ellie-app.com/39PpTzWp5y3a1/0
https://ellie-app.com/39PpTzWp5y3a1/1
Post by Frank Bonetti
If you batch a list of commands, they will be executed in reverse order.
Cmd.batch
[ Task.perform (always (AddString "first")) (Task.succeed ())
, Task.perform (always (AddString "second")) (Task.succeed ())
, Task.perform (always (AddString "third")) (Task.succeed ())
]
third
second
first
Has anyone else noticed this behavior? If so, do you know why Cmd.batch
was implemented this way?
https://ellie-app.com/39NxYQfDCHMa1/0
--
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
For more options, visit https://groups.google.com/d/optout.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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
2017-05-12 16:33:15 UTC
Permalink
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.
Post by Peter Damoc
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)
https://ellie-app.com/39PpTzWp5y3a1/0
https://ellie-app.com/39PpTzWp5y3a1/1
Post by Frank Bonetti
If you batch a list of commands, they will be executed in reverse order.
Cmd.batch
[ Task.perform (always (AddString "first")) (Task.succeed ())
, Task.perform (always (AddString "second")) (Task.succeed ())
, Task.perform (always (AddString "third")) (Task.succeed ())
]
third
second
first
Has anyone else noticed this behavior? If so, do you know why Cmd.batch
was implemented this way?
https://ellie-app.com/39NxYQfDCHMa1/0
--
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
For more options, visit https://groups.google.com/d/optout.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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
2017-05-12 16:46:25 UTC
Permalink
Post by Frank Bonetti
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
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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
2017-05-12 17:07:56 UTC
Permalink
Post by Peter Damoc
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?
Post by Peter Damoc
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.
Post by Peter Damoc
Post by Frank Bonetti
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
https://github.com/elm-lang/core/issues/730#issuecomment-253584490
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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
2017-05-12 17:45:49 UTC
Permalink
Post by Frank Bonetti
Post by Peter Damoc
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
<https://github.com/elm-lang/core/blob/master/src/Native/Platform.js#L311>.

Elm's JS kernel code is quite readable.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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
2017-05-12 17:52:27 UTC
Permalink
Post by Peter Damoc
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!
Post by Peter Damoc
Post by Peter Damoc
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
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
<https://github.com/elm-lang/core/blob/master/src/Native/Platform.js#L311>.
Elm's JS kernel code is quite readable.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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.
Max Goldstein
2017-05-12 16:46:53 UTC
Permalink
"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.
--
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
2017-05-12 17:03:24 UTC
Permalink
Post by Max Goldstein
"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.
Post by Max Goldstein
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.
Post by Max Goldstein
"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.
--
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
2017-05-12 17:49:08 UTC
Permalink
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.
--
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
2017-05-12 18:03:22 UTC
Permalink
Post by Frank Bonetti
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.
This SO answer might have relevant info
<http://stackoverflow.com/a/561068/626515> .
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/
--
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.
Loading...