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!
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!
The answer to your question is best illustrated by example.
I have an xmas function like this:
wrapPresents : presents -> String
But what is the type, presents
? It can be anything that you want! You can substitute it with your own 'concrete type'. presents
is just a place holder!
wrapPresents : Boxes -> String wrapPresents boxes = "All the boxes are now wrapped!"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With