What is the advantage of a translationDictionary in the "Translator pattern"?

I’m struggling with the common “Child-to-Parent Communication Problem” in my codebase and came across https://medium.com/@alex.lew/the-translator-pattern-a-model-for-child-to-parent-communication-in-elm-f4bfaa1d3f98 which seems to be the accepted pattern in the community.

I’m scratching my head trying to understand why a TranslationDictionary on the following lines is required:

type alias TranslationDictionary msg =
  { onInternalMessage: InternalMsg -> msg
  , onPlayerWin: Int -> msg
  , onPlayerLose: msg
  }

I’ve come up with a simpler solution and wanted to understand if I’m missing anything (which I will only find out once I’ve written a couple of thousand lines of code!). Here’s my simplified approach:

-- Child.elm
type Msg
  = ChildMsg1
  | ChildMsg2

type ParentMsg
  = ParentMsg1
  | ParentMsg2

view : (ChildMsg -> msg) -> (ParentMsg -> msg) -> Html msg
view childConstructor parentConstructor 
  = div [] [ button [ onClick (parentConstructor ParentMsg1) ] [ text "send msg to parent" ]
           , button [ onClick (childConstructor ChildMsg1) ] [ text "send msg to child/self" ]
           ]

-- Parent.elm

type Msg
  = Msg1
  | MsgForChild Child.Msg
  | MsgForParent Child.ParentMsg

view : Model -> Html Msg
view = div [] [ Child.view MsgForChild MsgForParent ]

What kind of problems can I expect with this simplified approach? Consequently. why is the transalationDictionary required in the first place?

1 Like

What you did looks like a variation of the OutMsg pattern to me. In the translator pattern the parent tells the child what it can do, while in the OutMsg pattern it is the child who tells the parent what it can do. In your case you define in the child the different ParentMsg.

At a first glance, I like what you did because it simplifies the pattern a lot.

I just wanted to react to “this seems like the accepted pattern in the community”.

I very strongly recommend against thinking about your code in terms of parent/child components. This mindset makes more sense in JavaScript where you have components (i.e. local state + methods, i.e. objects) but I think it leads to a lot of unnecessary complexity in Elm. The instinct to preemptively draw lines with an object-oriented mindset is positively correlated with expertise with imperative languages, and while it is helpful there, it ends up being a detriment in languages like Elm in my opinion and experience.

I wrote https://guide.elm-lang.org/webapps/structure.html to try to encourage other ways of approaching code organization that I find work better in functional languages without side-effects. Maybe it can be somewhat helpful.

4 Likes

I’ve seen Richard Feldman recommended this “translator pattern” in his talks though, and noredink-ui for example uses it in many places:

https://package.elm-lang.org/packages/NoRedInk/noredink-ui/latest/Nri-Ui-Checkbox-V5
https://package.elm-lang.org/packages/NoRedInk/noredink-ui/latest/Nri-Ui-TextInput-V5
https://package.elm-lang.org/packages/NoRedInk/noredink-ui/latest/Nri-Ui-SegmentedControl-V7

I think the “bad thing” is rather to make UI components with internal state when they actually don’t need it.

1 Like

Hm, there must be a misunderstanding here. I’m quite sure I’ve never in my life said the phrase “translator pattern” out loud - let alone in a talk!

I completely agree with Evan’s comment here:

My main goal for this keynote at Elm Europe 2017 was to explain what I recommend instead of thinking in terms of parent/child components.

Over the years I’ve noticed a common response to this has been “how can you say you are against this, when you are using it [at NoRedInk/in elm-spa-example/etc]?”

Something I tried to be really clear about in this talk is that Html.map and Cmd.map are absolutely useful tools—there is no problem with anyone using them!—but they should be used only when simpler alternatives wouldn’t work rather than as the first tool to reach for.

So seeing them in use in the wild is not evidence of any problem. The problem is when they are used eagerly rather than as a last resort!

2 Likes

I think you called it “teach me how to something…” instead of “translator pattern” but isn’t it the same?

Ah, so the talk you’re referring to is the keynote I linked in the previous post. I think these are completely unrelated ideas.

The linked article in OP presents an idea the author has “been playing around with.” I think experimentation is healthy, but personally I recommend against adopting this particular experimental idea.

1 Like

Ohhh, sorry. I had read about the “translator pattern” earlier, and in my memory I must have equated it with the much simpler "view function that takes a record with parameters, some of which are a -> msg" - which is what the examples from noredink-ui I linked to are using. :slightly_smiling_face:

Yeah the pattern in the blog post is not something I’ve seen used.

1 Like

Hi Evan, thank you for pointing me to this. I have probably read the 0.18 version of this page (or a similar page) earlier. I have read it again, and have the following to say:

  • I agree with your point about not forcing unnecessary structure by dividing the code into modules artificially (especially by up-front thinking). Refactoring in Elm is easy and one should let the code structure evolve (I spend a lot of time with Haskell, and can completely relate to this point). I also appreciate the fact that at a page-level, trying to force code re-use may not be possible, because pages maybe be similar, but not the same.
  • Now, having said that, I’m not completely convinced with last section about “Components”. In React everything is a component whereas everything may not be a component in Elm - so there is some unlearning that React programmers need to do here. However, that doesn’t mean that nothing is a component in Elm. Surely, some UI elements/widgets need to have some sort of box drawn around them where one don’t bother with the internals of what is happening inside the box. Here are some examples I can think of: date-pickers, autocomplete widgets, buttons which trigger an AJAX call enable/disable themselves based on state of the call, similarly buttons with a progress bar based on state of AJAX cal, global progress bar that reacts to any AJAX call on the page, global widget to display success/error notifications that user can dismiss (or they auto-dismiss after X seconds). In each of these cases there is either (a) some message that needs to be delivered to the widget without the main app being bothered about it, or (b) the widget needs to tell the main app about some interesting event/data. This is what I meant by “Child-to-parent communication problem”

If you feel I am rehashing points that have been discussed multiple times, please feel free to point me to some older discussion / write-ups. I’d be happy to do some more homework before coming back.

PS: I’m going through https://www.youtube.com/watch?v=DoA4Txr4GUs&feature=youtu.be and will write back if I change my mind about what I have said above.

1 Like

Yeah you should definitely avoid stateful components whenever possible, but when you do need them - like for pages, complex modals, and the widgets you listed, it’s nice to have a consistent pattern I think. For simple modals for example I just handle the state in the parent.

I recently started using this package which I think is pretty neat: https://package.elm-lang.org/packages/z5h/component-result/latest/

It’s basically the standard pattern that elm-spa-example uses, but it has this custom type for it:

type ComponentResult model msg externalMsg err
    = ModelAndCmd model (Cmd msg)
    | ModelAndExternal model externalMsg (Cmd msg)
    | JustError err

Your child update function looks like this:

MouseEnter index ->
    CR.withModel (Model { state | index = index })

Selected maybeKey ->
    CR.withModel init
        |> CR.withExternalMsg maybeKey

And this is how you handle it in the parent:

CompanySelectMsg selectMsg ->
    Select.update selectMsg model.companySelect
        |> CR.mapModel (\childModel -> { model | companySelect = childModel })
        |> CR.mapMsg CompanySelectMsg
        |> CR.applyExternalMsg
            (\companyId ->
                CR.mapModel (\m -> { m | companyId = companyId })
            )
        |> CR.resolve

It’s still fairly verbose, so you still want to avoid using it when not necessary, but I’ve found it’s pretty simple and quick to write - feels nicer than when I was using a plain tuple like (childModel, childCmd, Maybe outMsg). It’s also more type safe - you’ll get a type error if you forget to handle something.

@mendress wrote a blog post about how we handle parent-child communication in our applications: https://www.curry-software.com/en/blog/elm_shared_state/

Maybe this is helpful for some.

1 Like

I recommend to just return whatever you want to send to the parent from your component’s update function. For example update : Msg -> State -> ( State, Maybe OutMsg ) where OutMsg is a type that lists all possible events that the parent can react on, or it can be anything else. It’s just functions that return values after all. It’s a very simple technique that works for me well.

And the “translator pattern” has one issue by the way: you can’t easily send a msg both to the parent and to the child in response to a single event.

Thanks for posting this @JDemler Just reconfirming what I understood from the blog post:

  • This is about some app-wide state that multiple pages need to share, eg. list of issues currently loaded in the UI (directly from quoted from your blog post). Another could be - a very large model which is edited/altered by different pages, each page being responsible for a subset of the model BUT in certain cases an edit in one subset may lead to an automatic change in another subset.
  • This is not directly about “internal state” of a small-scale widget that the main application doesn’t really need to bother about.

@edkv I had considered this as well. It works well when the child needs to tell the parent about an interesting event. However, it leads to unnecessary boilerplate when the child needs to emit an event that is completely internal to it (and the parent needn’t bother about it).

Probably this snippet can explain what I’m trying to say:

updateOfParent : Model -> Msg -> (Model, Cmd Msg)
updateOfParent model msg =
  case msg of
    MsgAForChild -> 
     let (newModel, msgFromChild) = Child.update model 
          finalMsg = case msgFromChild of
            MsgForParent -> -- do something
            MsgAPrimarilyForChild -> -- what to do about this?
     in ( newModel, finalMsg )

Child.Msg is for internal events.
Child.OutMsg is for events that the parent is intrested in.

So you get:

-- Parent.elm

type alias Model =
    { ...
    , child : Child.Model
    }

type Msg
    = Msg1
    | Msg2
    | ...
    | ChildMsg Child.Msg


update : Model -> Msg -> (Model, Cmd Msg)
update model msg =
    case msg of
        Msg1 ->
               ...

        Msg2 ->
               ...

        ChildMsg childMsg ->
            let
                ( newChildModel, outMsg ) =
                    Child.update childMsg model.child 
       
                -- Do something in response to outMsg.
                ... =
                    case outMsg of
                         Just Child.OutMsg1 ->
                                ...
                         Just Child.OutMsg2 ->
                                ...
                         Nothing ->
                                ..
            in
            -- Update model, don't forget to set child model to newChildModel, return cmd, etc.
            ...
   

You got it right! That’s about it.

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.