2

I have a question about merging multiple effects. My goal is to run N commands (eg Random or Http) in parallel, then merge the results and kick off an update. Think of it an an extension of the Elm Architecture Dice exercises for N dice.

I understand you can do this with Task.map2 or Task.sequence, but I’m looking specifically for parallel execution.

Cmd.batch sounds like the parallel part I want, but I can’t figure out how to merge the results of the executions.

Here’s my full Elm code and here's how I'm guessing at transforming it to act in parallel (doesn't work).

For what it’s worth, in JS I’d accomplish this with Promise.all

Promise.all([promise1, promise2, …]).then(resultsList => …)

Can’t find anything on this topic online except this, which sidesteps the question: How to perform multiple Http requests (Tasks) in bulk in Elm lang and this which avoids multiple effects: How do I add a second die to this elm effects example?. Hoping this isn't still the case: Is there parallelism in Elm? (2015 answer was no).

mLuby
  • 673
  • 7
  • 15

2 Answers2

2

Process.spawn will allow you to kick off multiple tasks that the runtime may interleave, however there appears to be no way to end up with a single Msg a where a is some combined result of a number of parallel tasks together.

The notes on the Process page under Future Plans seem relevant to your question.

If you'd like to further explore what's possible now and the behavior of spawn, I've created an extension of the more-cats HTTP example app, porting some of the helpers from elm-task-extra (which hasn't been updated for 0.18).

Mario
  • 615
  • 7
  • 7
2

With the Elm Architecture, you can fire off a bunch of tasks at once using Cmd.batch, but when the results are ready, they will come in as single Msg values in the update function, and in no guaranteed order.

Therefore, (at least as of Elm 0.18), you will need to handle "merging" the results yourself since the Msgs you fire off will come in one by one. In your Dice example, you would want a single Msg constructor for setting a dice value at a particular array index (in this example I used Array instead of List because you'll be setting a lot by index):

type Msg
    = Roll
    | NewFaceAt Int Int

Now you just need to handle that NewFaceAt constructor in your update cases:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        Roll ->
            ( model
            , Cmd.batch
                (Array.toIndexedList model.dice
                    |> List.map (\( i, _ ) -> Random.generate (NewFaceAt i) (Random.int 1 6))
                )
            )

        NewFaceAt index newFace ->
            ( { model | dice = Array.set index newFace model.dice }, Cmd.none )

Here is a working example of the above on ellie-app.com.

If you wanted to more closely resemble the Promise.all([...]).then(...) example, you would have to bake in some sort of status flag on your model that would be updated every time you receive NewFaceAt. For example, your dice could be a list of Maybe Int, then when you roll, set them all to Nothing, and as dice values come in, you set them to Just value and then check to see if all dice values are filled. You could write your view in such a way that you show a "loading" message while the results are still unknown, and only show the images when all results are in. Here is an example that waits until all results are ready (granted, it happens so fast that you won't see the loading screen; the effect is more visible when dealing with multiple Http requests).

Chad Gilbert
  • 36,115
  • 4
  • 89
  • 97