Hi,
Kind of difficult to understand what you are really trying to do, by looking at a contrived example made up of A1 and A2, but…
My initial thought is that there is no difference between your versions and different pages in an SPA. You pages A1 and A2 have different models, different views, and take different messages. You could describe them as version 1 and 2 of A, or you could just describe them as 2 entirely separate and unrelated pages of an SPA.
In which case, you can just wire them up within the Main module, as nested TEAs, exactly as the pages of the SPA example are.
You are trying to hide the fact that there are 2 version of A from the Main module, abstracting their messages as the type A.Msg. But then you need an intermediate module, A, that reveals A.Msg to be either A1Msg or A2Msg. At some point you have to state what the types of messages for A1 and A2 are, so I don’t know if you gain much by trying to hide this, only to have to deal with it in an intermediate module.
No, there are always more ways of doing things.
It is not the simplest pattern for sub-views, and other patterns should be tried first. However, once you reach a certain level of complexity and your sub-views really start to need to manage their own state, then it becomes inevitable. We can’t describe something that is often necessary as an anti-pattern; just a pattern that we should not reach for straight away.
===
If your versions had more in common, there are ways that you could present them so that you can have different implementations of the same functionality.
For example, suppose I have multiple versions of something that render some Content. When new content is loaded from the back-end, the Model needs updated to incorporate the new Content. I could define an API like this:
module ContentPage exposing (..)
type alias ContentModel model =
{ addContent : Content -> model -> model
, changeColor : Color -> model -> model
, changeSize : Size -> model -> model
}
module A1 exposing (..)
init : ContentModel A1Model
module A2 exposing (..)
init : ContentModel A2Model
Now both versions have a common API, with nearly the same type. So either version can be substituted, and the Main.update function can manage both versions without knowing too much about their internal details (it does need to understand that the types A1Model and A2Model are distinct though).
You could then write a more generic update function that abstracts over the model type parameter, but can process messages for either version:
type Msg = AddContent Content | ChangeColor Color | ChangeSize Size
update : ContentModel model -> Msg -> model -> model
update contentModel msg model =
case msg of
AddContent content -> contentModel.addContent content model
_ -> -- You get the idea.