Separating the `Cmd Msg` from `init`

I made my decoders Decode (Model, Cmd Msg) and I ended up having to fetch out the Cmd Msg from weird places in the data structures.

It occurred to me that making init : Model and then adding initCmd : Cmd Msg instead of a tuple. It would actually simplify a lot of code, not just decoding. Like initializing nested components where I have to let (child, childCmd) = Child.init in .... If the initial messages depend on the model, I can use initCmd : Model -> Cmd Msg.

Does anyone have experience doing that? Will I run into unforeseeable problems?

Weird places sounds like you’re relying on implementation details - I’m guessing that it’s something with node in it, that really doesn’t sound like a good idea. As for splitting your init into multiple stages using multiple functions: I see nothing wrong with that, it’s all just pure functions.

What do you mean by “node”? Coincidentally, my records do live in a graph, meaning they are wrapped up in Node records. If that’s what you meant, how the hell did you know? :laughing: What exactly doesn’t sound like a good idea?

It’s not that simple. Will the initial Cmds end up depending on the initial model or something like that? Why doesn’t anyone do it like that despite it greatly simplifying a lot of code?

nested components

I suspect this is the root of the overcomplication, and init feeling overcomplicated is a symptom.

I’d recommend this talk for specifics on how to avoid the “nested components” antipattern!

I’ve found it useful to make the JSON for a particular page or AJAX request not depend on lots of modules. Instead, have your program’s init use a decoder that defines the JSON structure for that entire program, and then have init functions for your smaller data structures take in values that you’ve already decoded. This makes your JSON schema less coupled to the UI and logic of your program, and gives the interfaces between your modules more stability.

JSON decoders are easy to compose, so it’s simpler to build the decoder you need at the top level than to build and maintain a hierarchy of modules that have complex interactions in how they decode and initialize. When defining modules, it’s generally better to expose small pieces that can be assembled by the caller than to create an intricate pattern that all your modules must follow.

Also, I agree with rtfeldman–if you have more than a couple non-program modules that produce commands when they initialize, it’s likely you can simplify a lot of your views to make your project easier to work with.

3 Likes

I had no idea nested components were an antipattern, I feel like they were encouraged a few years ago. I have watched the talk and I agree, but I’m not sure how to incorporate those ideas into my app.

I do have my inits take in the decoded values. I dislike having all of the decoding happen in one place, but maybe that’s because I have components all over the place. I like how each of them only needs to worry about knowing how to serialize itself.

Huh, you’re probably right. I do have some complex decoding interactions that bother me.

I didn’t mention this in my first reply because it can be hard to explain the distinction, but you could still put reusable decoders in your other modules if there are clear data types that make sense as domain concepts:

For example, if you have a module for a “friends list” view, what I’m cautioning against is having that module define an arbitrary json structure:

-- example of what to avoid doing:
module FriendsList exposing (Msg, State, decoder, view)

decoder : Decoder (State, Cmd Msg)
view : State -> Html Msg

-- another example of what to avoid:
module FriendsList exposing (Flags, Msg, State, decoder, init, view)

decoder : Decoder Flags
init : Flags -> (State, Cmd Msg)
view : State -> Html Msg

In the above examples, the decoders are meant to be reusable, but they will be hard to maintain and are an unstable interface because they don’t really have any intuitive meaning as a domain concept.

In contrast, perhaps the idea of a “Friend” does make sense as a domain concept, so you might end up with a reusable decoder like this:

-- example of what might be better:
module FriendsList exposing (Friend, State, friendDecoder, view)

friendDecoder : Decoder Friend
init : State
view : List Friend -> State -> Html msg

Now the module is providing a decoder for a useful type instead of for a meaningless, arbitrary type. This can make your top-level decoder have less code while still letting the top-level have control over the JSON schema and decoupling it from the implementation of the other modules.

Though with that said, JSON decoders tend to be low-risk to maintain, so personally I’d probably start by making a potentially overly-verbose top-level decoder, then spend the most time focusing on cleaning up your module hierarchy and reducing the number of places you have to pass Cmds and Msgs around, and then if there’s more time after that, take a look at refactoring your top-level decoder.

2 Likes

Thank you for the more detailed explanation. I do probably have a few superfluous decoders that I should remove, under that criterion.