Having seen code written in the “product style” (described as its author as being written in the recommended style for Elm — see footnote) and having written a lot of code in the sum style, I can make the following observations:
• The sum style has a lot of plumbing to dispatch to sub-models since the message types also tend to end up as sums. It’s straightforward plumbing — I’ve found I can write it really quickly — but there can be a lot of it once you get subscriptions and update involved and it gets worse if there are more standard glue connections like we have.
• The product style avoids this and handles keeping parts separate by using separate update functions for subsets of the messages. This, however, has a couple of problems. If the message type is kept flat, then the update pattern is just to invoke all of the partial updates with the message and let the ones that actually apply do their work. That’s cool but it means that you lose the completeness checking on update case statements leading to “what happened to this message” questions. The signature of the update functions can also make it fuzzy as to what data can be changed by the update and what is just read but this has more to do with issues of how context is conveyed.
Refactoring is also an interesting question in these styles. The sum style leads to code in which it is pretty easy to move the sub-models around, have two copies of a single sub-model, etc. The product style has to go through the separation process if you find you need that. YAGNI argues for not putting all that boilerplate upfront. On the other hand, refactoring often takes careful planning for how to make the move in small steps while writing according to a plan doesn’t require as much focused concentration.
Finally, a related issue not addressed directly with either of these styles is that if your sub-models can come and go and come again, then in an async world, you really need to put in place some guards against receiving messages intended for a previous incarnation of the sub-model. I tend to handle this by storing the sub-models in either form with an Int generation number and a Maybe around the sub-model. Transitioning from Nothing to a sub-model increases the generation number. Updating an existing sub-model leaves the generation number unchanged. I use a sub-message type to go with the sub-model type and include the target generation number in the tagger constructor wrapping the sub-message — i.e., ToSubModel Int SubMsg.
Mark
Footnote: Actually the style I’ve seen advocated as being “recommended Elm” flattens sub-models into the model itself and use record extensions to identify the sub-model fields. This tends to push the pros and cons of the product style even further.