19

I'm trying to modify a simple app from the elm-lang tutorial to first update the model, then trigger another update.

update msg model =
  case msg of
    MorePlease ->
      (model, getRandomGif model.topic)

    NewGif (Ok newUrl) ->
      ( { model | gifUrl = newUrl }, Cmd.none)

    NewGif (Err _) ->
      (model, Cmd.none)

    -- my addition
    NewTopic newTopic ->
      ({ model | topic = newTopic}, MorePlease)

This fails in the compiler because the NewTopic branch:

The 3rd branch has this type:

( { gifUrl : String, topic : String }, Cmd Msg )

But the 4th is:

( { gifUrl : String, topic : String }, Msg )

So my Msg needs to be type Cmd Msg. How can I turn" my Msg into a Cmd Msg?

note: I recognize there is a simpler way to make this change, but I'm trying to understand Elm more fundamentally

glennsl
  • 28,186
  • 12
  • 57
  • 75
steel
  • 11,883
  • 7
  • 72
  • 109

2 Answers2

41

There is really no need to turn Msg into a Cmd Msg. Remember that update is just a function, so you can call it recursively.

Your NewTopic case handler can be simplified to this:

NewTopic newTopic ->
    update MorePlease { model | topic = newTopic}

If you really truly wanted the Elm Architecture to fire off a Cmd for this scenario, you could do a simple map of Cmd.none to your desired Msg:

NewTopic newTopic ->
    ({ model | topic = newTopic}, Cmd.map (always MorePlease) Cmd.none)

(not actually recommended)

Chad Gilbert
  • 36,115
  • 4
  • 89
  • 97
  • Thank you. This is an easier way to solve the problem. I still wonder, would I ever need to "cast" a `Msg` to a `Cmd Msg`? And how would I if I did? – steel Mar 09 '17 at 19:13
  • @ChadGilbert: Could you elaborate a bit more why you would not recommend the second approach by firing new `Msg`? – DanEEStar May 25 '17 at 21:45
  • Returning a `Cmd` will cause another cycle of the Elm Architecture lifecycle, so you're really just spending extra CPU cycles that you don't need to waste if it's just to fire off another Msg. There may be reasons you want to force another event cycle (like animation or other view updates, but even those require extra code). For the most part, forcing an unnecessary `Cmd`, IMO, feels like it's missing the underlying nature of the `update` function as being a function, and misunderstanding the event cycle. – Chad Gilbert May 25 '17 at 22:19
  • Is there a possibility to pass some argument with the `MorePlease` message? – Oskar Szura Jun 20 '17 at 20:10
  • 2
    Yes, anything that results in a `Msg` can be used. E.g. `Cmd.map (always (SomethingElse param1 param2)) Cmd.none` – Chad Gilbert Jun 20 '17 at 20:17
  • I was having a similar issue understanding this fundamentally. I had split update into multiple functions and selected them based on a union type. Once a user logs in, the type changes and a new update function will then be used. I have some data that can be refreshed and I want it to be refreshed on login automatically so I was trying to send the Refresh message through the event system. What I really needed to do was simply use the function that generates the command in the other update function which I had helpfully named refresh but failed to realize I could use to refresh the state. – James Andariese Sep 19 '18 at 05:34
  • Using `Cmd.map` seems to no longer work in 0.19. `Task.perform`, as @Deimos suggests below does. – glennsl Nov 01 '18 at 21:47
14

Add the following function:

run : msg -> Cmd msg
run m =
    Task.perform (always m) (Task.succeed ())

Your code would then turn into:

NewTopic newTopic ->
      ({ model | topic = newTopic}, run MorePlease)
Deimos
  • 1,835
  • 1
  • 16
  • 15