Newbie struggling with (Element msg) vs (Element Msg)

I’m having a type mismatch error. Now I know that this is because the type signutares are mismatched, but if I change the message type, then it causes the same errors all the way down to my main program.

So my question is, in the column below, my Input.text (elm-ui inputs) return my custom Settings.Msg message as part of the onChange handler. How do I get around this limidation?

This is the Error:

-- TYPE MISMATCH -------- /home/andre/dev/cazinc/freeman/assets/src/Settings.elm

Something is off with the body of the `view` definition:

 84|>    column
 85|>        UI.frameBase
 86|>        -- [ el [] <| text model.auth_id
 87|>        -- , el [] <| text model.auth_secret
 88|>        -- , el [] <| text model.refresh_token
 89|>        -- ]
 90|>        [ Input.text
 91|>            []
 92|>            { label = Input.labelLeft [ centerY ] <| text "Auth ID"
 93|>            , text = model.auth_id
 94|>            , onChange = AuthIdChanged
 95|>            , placeholder = Nothing
 96|>            }
 97|>        , Input.text
 98|>            []
 99|>            { label = Input.labelLeft [ centerY ] <| text "Auth Secret"
100|>            , text = model.auth_secret
101|>            , onChange = AuthSecretChanged
102|>            , placeholder = Nothing
103|>            }
104|>        , Input.text
105|>            []
106|>            { label = Input.labelLeft [ centerY ] <| text "Refresh Token"
107|>            , text = model.refresh_token
108|>            , onChange = RefreshTokenChanged
109|>            , placeholder = Nothing
110|>            }
111|>        , Input.button
112|>            UI.buttonBase
113|>            { onPress = Nothing
114|>            , label = text "Save"
115|>            }
116|>        ]

This `column` call produces:

    Element Msg

But the type annotation on `view` says it should be:

    Element msg
-- Settings.elm

-- ...

view : Element msg
view =
    let
        model = tempData
    in
    column
        UI.frameBase
        -- [ el [] <| text model.auth_id
        -- , el [] <| text model.auth_secret
        -- , el [] <| text model.refresh_token
        -- ]
        [ Input.text
            []
            { label = Input.labelLeft [ centerY ] <| text "Auth ID"
            , text = model.auth_id
            , onChange = AuthIdChanged
            , placeholder = Nothing
            }
        , Input.text
            []
            { label = Input.labelLeft [ centerY ] <| text "Auth Secret"
            , text = model.auth_secret
            , onChange = AuthSecretChanged
            , placeholder = Nothing
            }
        , Input.text
            []
            { label = Input.labelLeft [ centerY ] <| text "Refresh Token"
            , text = model.refresh_token
            , onChange = RefreshTokenChanged
            , placeholder = Nothing
            }
        , Input.button
            UI.buttonBase
            { onPress = Nothing
            , label = text "Save"
            }
        ]

-- ...
-- AppLayout.elm

-- ...

-- Main Content
contentPanel : Element msg
contentPanel =
    column
        [ width fill
        , height fill
        , centerX
        ]
        [ Settings.view ]

-- Build the App layout
main : Html msg
main =
    layout
        [ Background.color Theme.color.gray0
        , Font.color Theme.color.gray8
        , Font.size 16
        ]
    <| row
        [ width fill, height fill ]
        [ sidePanel
        , contentPanel
        ]

I’m not sure I understood correctly, but I think you simply have to adjust your definition of the view function. I think it should be

view : Element Msg
view =
  ...

Note the difference being, that the Msg part now is starting in upper case.

Reason: I think it’s because you are telling the elm compiler “hey, I have a View-function that returns an generic Element (aka Element a)”. Elm then goes and realizes "Uhm, no, you actually have an Element that possibly sends a message of your Msg-type (aka Element Msg).

It’s a bit like how you can say “I have a generic list of something” which in Elm is List a (or List something or List bla) - anything that’s List + lowercase.

If you want to have a specific list you need to write it’s type in uppercase, so e.g. List Int or List String or List (Element msg) (as you can see, nesting generic and non-generic is possible).

To go back to your example: When your view uses your message type, you need to reference it by using its uppercase name. But often a View-function does not actually send a message and then it’s often common to just write Element msg to say “It possibly sends some generic message”.

I understand that part yes, but my issue is now that If I do change the signature to return Element Msg, then this issue is just pushed further up to the next function (inside Main Content) It will then raise an error there, saying that it’s a type mismatch. So then I have to change the signature for contentPanel to also return Element Settings.Msg, which then pushes the problem further cause then it complains about the layout and row function s in main.

This is not what I want, especially since I later want to add other view s to contentPanel, which could have their own msges as well.

You have main : Html msg. When main is just html, your program isn’t interactive. So even if you solve the msg puzzle, nothing will happen when you click buttons or so. You need to upgrade to Browser.sandbox or Browser.element.

Once you’ve done that, you’ll have something like main : Program flags model msg. Then you’ll notice that we’re not really interactive this time either, because if the top level program has a generic msg you can’t do anything with it. (A function receiving a generic parameter can’t inspect it, or create a new one, only pass it along to other things.)

So you’ll want to define a toplevel Model and Msg type.

Once you’ve done that, we can finally talk about your original question.

It sounds like you want view : Element Msg in Settings.elm like @IloSophiep says after all. In contentPanel, you can do something like this:

type Msg
    = SettingsMsg Settings.Msg
    | OtherCoolThing

contentPanel : Element Msg
contentPanel =
    column
        [ width fill
        , height fill
        , centerX
        ]
        [ Settings.view |> Element.map SettingsMsg ]

However, it’s easy to get lost in all the different Msg types in different modules, and all the wrappings of them. So it’s generally better to start out with a single Msg type that you use for everything, and refactor later. There’s no gain in trying to make little “TEA modules” out of everything – just extra work. I find it much easier to refactor afterwards. Otherwise it’s easy to overcomplicate. When extracting a module, you can choose to have its own message type in there (for more advanced things), or take all messages as parameters (for simpler cases).

1 Like

Thanks for the detailed info.

Universal Msg type for the entire application is a good idea. Could serve as a single reference of everything the app does as well.

I’ll give this a go.

For component-like reusable views, one option is to take a group of messages as an argument. Richard Feldman’s “Scaling Elm Apps” talk discusses the pattern here:

He also talks about Html.map, which is a way to keep using a separate msg type for your Settings module.

OK I marked @lydell 's answer as the solution, but really everyone here had a hand in helping me with the issue. I used all the information and was able to get that (and other stuff) working based on the info you guys gave.

Unfortunately, I can only mark one accepted solution :wink:

Thx!

4 Likes

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