Thanks for experimenting with this! Sorry it’s taken so long to respond (and that I’ve been absent on Slack) — I’ve been traveling & busy with work.
I have two answers to your question. I’ll start with the one you’re less likely to buy: catch the message in the child and then, from the child, use a command to generate a `ForParent SayText` message. The intuition here is that to the child, having the text said aloud is an external side effect; the child broadcasts that it has text to say, and that message is caught and interpreted by someone else (the structure of whose code the child does not know). If you think of a simple button as a component, it’s doing something similar when clicked: handling internal logic related to its view, then asynchronously generating a message for the parent. But I know you’re not a big fan of this model; we’ve discussed before and I doubt I’ll convince you here!
So here’s answer 2. The main contribution of the Translator Pattern, I think, is the idea that when you call `Html.App.map` and `Cmd.map`, there’s no need to pass a “dumb tag” that’s applied to all child messages indiscriminately. In this article, I discuss one way to create “smart tags” or “translators” — but it’s only one way, and it’s best suited to the case where child UI elements or HTTP requests are generating information that should go directly to the parent. There are other ways you could construct the translator function too.
Here’s one architecture idea:
The parent message type is defined as
type alias Msg = (SelfMsg, List ChildMsg)
type SelfMsg = Things | The | Parent | Responds | To
type ChildMsg = Component1 C1.InternalMsg | Component2 C2.InternalMsg | …
The parent defines three update functions:
update : Msg -> Model -> (Model, Cmd Msg)
; handles child messages (calling updateChild) and then parent messages, composing model changes and commands. This could be provided by a library.
updateChild : ChildMsg -> Model -> (Model, Cmd Msg)
; dispatches a child message to the correct component
updateParent : SelfMsg -> Model -> (Model, Cmd Msg)
; handles a parent message
In the child, you can produce a list of messages (type alias Msg = List Msg’, type Msg’ = ForSelf … | ForParent …), some for yourself and some for your parent. This is translated into the correct parent messages and child messages by a custom ‘translator’ function.
I don’t know if that’s the most elegant, but it _is_ totally pure, and demonstrates that there’s a good amount of unexplored design space. Let me know if this sketch is unclear — happy to expand on it!
Of course, all these heavier-weight architectures are trying to solve very general-purpose problems, imagining that you need a robust solution to handling lots of child components that may need to communicate often to their parents. Depending on your app’s specifics, that could be a code smell. And in smaller apps, it doesn’t make sense to over-architect when you can use something simpler (like OutMsg).