My main program has an update
function of
update : Msg -> Model -> ( Model, Cmd Msg )
To communicate with sub-components we can add another variant and wrap our messages in a new message
type alias Model =
{ ...
, child : Child.Model
}
type Msg
= ...
| ChildMsg Child.Msg
update msg model =
case msg of
...
ChildMsg childMsg ->
let
( childModel, cmd ) =
Child.update childMsg model.child
updatedModel =
{ model | child = childModel }
childCmd =
Cmd.map ChildMsg cmd
in
( updatedModel, childCmd )
However this seem challenging if the type of my sub-component's update
function does not match the parent. Consider a child with a polymorphic update function:
-- PolymorphicChild.elm
update : Msg a -> Model -> ( Model, Cmd (Msg a) )
When running a command from this module, I must wrap it
PolymorphicChild.someCommand : Cmd (Msg Foo)
PolymorphicChild.someCommand
|> Cmd.map PolymorphicChild
However, this produces a Msg (PolymorphicChild.Msg Foo)
, not the Msg PolymorphicChild.Msg
my App is expecting.
The right side of (|>) is causing a type mismatch.
(|>) is expecting the right side to be a:
Cmd (PolyMorphicChild.Msg Foo) -> a
But the right side is:
Cmd Polymorphic.Msg -> Cmd Msg
I tried adding a polymorphic parameter to App.Msg
-- App.elm
type Msg a =
= ..
| PolymorphicChildMsg (PolymorphicChild.Msg a)
But it basically blows up my entire program. Every function involving App.Msg
needs to somehow be changed to work with the new child component.
How can I unify the two types and get the two components working together?
I think the problem is that you're leaking too much information in your publicly exposed Msg
type. Your use of the type parameter of Msg a
seems limited to a known set of types, either an Author
, Category
, Post
, or Tag
. From skimming your code, it looks like it will never be anything but one of those four, so the fact that you are abstracting things in this manner should be kept inside of this module rather than exposing it and burdening any other code that may be pulling this in.
I think you need to move the abstraction down a level to avoid parameterizing your public Msg
type. I would suggest having four concrete constructors for Msg
instead of parameterizing it, and shift the abstraction down to a helper LoadInfo a
type:
type alias LoadInfo a =
{ worker : Worker a
, url : Url
, result : Result Http.Error ( Int, List a )
}
type Msg
= LoadPost (LoadInfo Post)
| LoadCategory (LoadInfo Category)
| LoadTag (LoadInfo Tag)
| LoadAuthor (LoadInfo Author)