elmparametric-polymorphism

What is the 'msg' in 'HTML msg' in Elm actually?


I'm teaching myself elm and see (of course) many references to Html msg--

I understand that this is a 'parameterised type', that is, that (as I understand it), the constructor of the type Html takes a parameter -- much as List Char does.

OK. But then in following along in some tutorials, I see they quickly change the msg to be a custom type, often something like this (I'm doing this from memory, so please forgive me):

Html Msg

where Msg might be defined as

type Msg =
  Something | SomethingElse

I also see that here -- https://discourse.elm-lang.org/t/html-msg-vs-html-msg/2758 -- they say

The lower case msg is called a type variable. The upper case Msg is called a concrete type - its something your application has defined.

That answers my question part way, but could someone elaborate on what this means exactly? Because when I see something like List String, I understand what String is in this context, but I don't understand what msg means in Html msg.

Also, are we not changing the type of the return value of the function? That is, if the Elm runtime is expecting view to return a certain type, and that type is Html msg, if we change the return type to Html Whatever, why does that work? (For instance, we can't change a function's return value from List String to List Number arbitrarily, right?)

Coming from a background in OOP and typed languages like C, TypeScript, etc, I would think that any Msg would need to somehow be related to msg, that is 'extend' it in some way to allow polymorphism. Obviously I'm looking at this the wrong way, and any explanation would be appreciated!


Solution

  • tl;dr: as long as every function agrees on the type of msg, and model, they can be anything you want.

    Just like the type parameter of List, msg can be anything. There is no restriction. To demonstrate, here's the classic increment/decrement example with the msg being just an Int:

    -- These type aliases aren't needed, but makes it easier to distinguish
    -- their roles in later type signatures
    type alias Model = Int
    type alias Msg = Int
    
    
    update : Msg -> Model -> Model
    update msg model =
        model + msg
    
    
    view : Model -> Html Msg
    view model =
        div []
            [ button [ onClick 1 ] [ text "+1" ]
            , div [] [ text <| String.fromInt model.count ]
            , button [ onClick (-1) ] [ text "-1" ]
            ]
    
    
    main : Program () Model Msg
    main =
        Browser.sandbox { init = 0, view = view, update = update }
    

    So how does that work? The key is in the Browser.sandbox function, as that's what connects everything together. Its type signature is:

    sandbox :
        { init : model
        , view : model -> Html msg
        , update : msg -> model -> model
        }
        -> Program () model msg
    

    where model and msg are type variables. Both of these can be replaced with any concrete type, as long as it matches up with the constraints given in this signature, which is that they must be the same across the init, view and update functions. Otherwise we'll get a type error here. If, for example, update requires a String instead of an Int, we'll get the error:

    This argument is a record of type:
    
        { init : Model, update : String -> Model -> Model, view : Model -> Html Msg
        }
    
    But `sandbox` needs the 1st argument to be:
    
        { init : Model
        , update : String -> Model -> Model
        , view : Model -> Html String
        }
    

    It doesn't really know which is right or wrong, of course, just that there's a mismatch. But in an effort to be helpful it assumes String is right and expects view to return Html String instead.

    I hope that sufficiently answers your question. If not, comment and subscribe!