How to turn a Msg into a Cmd Msg in Elm?

You can, but you probably shouldn’t

A question that occasionally pops up on Elm forums is “how can I create a Cmd with only a Msg?”

I had the same question at some point. What I wanted, was do an update with one kind of Msg, and then do another update of the model with another kind of Msg. But update does not output a Msg. It outputs a Cmd Msg. So my question was “How can I turn a Msg into a Cmd Msg?"

At the time, the helpful Elm community gave me an advice that was simply: “don’t”. And I’ve learned since that that was good advice. Here’s why.

What does a Cmd with only a Msg do?

Usually, when someone asks for a “Cmd with only a Msg”, they mean a command without side-effects. (When I asked this question, that’s what I meant, but I didn’t realize it at the time). This post is about such commands without side-effects. So commands that only affect your own code in Elm. No output to the outside world, and no input from the outside world.
There are also functions with the signature Cmd Msg that do have side effects. This post is only about side-effectless Cmd Msg. I labeled them Cmd Msg* (with the asterisk) in the titles to denote that the side-effect-less variant is meant.

Suppose you have an update function that can receive a message that a user has successfully logged in. Which adds a currentUser to your model.
And another branch in the update handles messages to show to the user. E.g. some popup window that displays a message for a couple of seconds.

type Msg = 
...
| LoginSucceeded User
| InfoMessage String
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
...
LoginSucceeded newUser ->
( { model | currentUser = newUser }
, Cmd.none
)
    InfoMessage message ->
( { model | message = Just message }
, Cmd.none
)

And now you want to display such a message after the user has successfully logged in. If you could make the LoginSucceeded branch include this as a command in its output, then the runtime would take care of it, right?

How Cmd Msg could work (theoretically)

The command with the InfoMessage would go to the elm runtime. The runtime would send it to the update function — together with our model — and update would make a new model with the new user and the message. Which would then be passed on by the runtime to the view function.

You actually can do this. If you use the awesome FancySearch and search for the signature msg -> Cmd msg you’ll get some libraries that already have this helper function built in. You can also make your own quite easily, using functions from the Task library.

-- in Elm 0.18
send : msg -> Cmd msg
send msg =
Task.succeed msg
|> Task.perform identity

And it would work. But I wouldn’t recommend it. Here’s why.

1. Cmd Msg* is inefficient

By creating your own Cmd Msg you are making 2 trips through the elm runtime. Which is unnecessary. The command you are sending out, will end up in some other branch of our update function. Which will make some change to our model (add a message in our example). Right?

Then why don’t we simply do this change directly from inside our LoginSucceeded branch?

-- better: recursive call to update
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
...
LoginSucceeded newUser ->
{ model | currentUser = newUser }
|> update (InfoMessage "you are logged in!")

-- better: directly update in branch
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
...
LoginSucceeded newUser ->
( { model
| currentUser = newUser
, message "you are logged in!"
}
, Cmd.none
)

It will save us a trip through the elm runtime. By doing all changes in one update cycle.

In practice, the efficiency difference between direct updates and using your own Cmd Msg is usually tiny. The Cmd Msg is already quite fast, so fast in fact, that the intermediate model state won’t even be rendered to the screen.

2. Cmd Msg* messes with the structure of your program

Typically, all the messages you define in your app are for events that happen outside your elm app (side effects), like sending and receiving data from a server or through javascript ports, getting current time or random data, or for handling user interaction in your view (yes, these are also side-effects).

If you create your own Cmd Msg, by definition this will not have any side effects. You are in effect directly sending instructions to elm to call your update function, providing it with all the info it needs right from your code.

When you use Cmd Msg you generate or simulate these events internally in your update function. Now if you find some bug later on, and you want to trace it, it will be a lot harder to find its origin from the update function. The Msg could have come from the outside world (including user interaction in your view), but could also have been generated by another branch in the update itself. This will make debugging harder.

3. Cmd Msg* has race conditions

Using Cmd Msg gives you NO guarantee about the model state your update will receive.

In the flow pictured above, the Cmd Msg we created was sent to our update function, together with the version of the model we created at that time.

Elm however does not guarantee this. It is perfectly possible that after we send our Cmd Msg, but before the next time update is called, some other message comes in. E.g. some other update from the server.

In our example, between the LoginSucceeded call and the InfoMessage call to our update our model may have changed (!).

This opens the door to some nasty bugs, which will be very hard to trace and fix later on.

Especially if you want to be sure that the Msg you are sending is performed immediately after the update from which it is created, and you expect the model to be unchanged from the model at some time, do not use Cmd Msg.

The Bottomline: You can produce your own side-effectless Cmd Msg in Elm, but think twice before you do. It is probably not the best solution for what you are trying to accomplish.

My adventures in Elm are still early. Do you have any use cases for which a Cmd Msg is appropriate? Let me know! I’d love to learn from others.

Show your support

Clapping shows how much you appreciated Wouter In t Velt’s story.