This is an example of nesting logic, where each child looks like an individual app. It knows nothing about what contains it or how it's run, and that's a good thing, as it allows for great flexibility in how things are put together.

Let's define our Counter module to hold child logic:

open Elmish

module Counter =

    type Model =
        {
            count : int
        }

    let init() =
        {
            count = 0
        }
        , Cmd.none // no initial command

    type Msg =
        | Increment
        | Decrement

    let update msg model =
        match msg with
        | Increment ->
            { model with
                count = model.count + 1
            }
            , Cmd.none

        | Decrement ->
            { model with
                count = model.count - 1
            }
            , Cmd.none

Now we'll define types to hold two counters top and bottom, and message cases for each counter instance:

type Model =
    {
        top : Counter.Model
        bottom : Counter.Model
    }

type Msg =
    | Reset
    | Top of Counter.Msg
    | Bottom of Counter.Msg

And our initialization logic, where we ask for two counters to be initialized:

let init() =
    let top, topCmd = Counter.init()
    let bottom, bottomCmd = Counter.init()

    {
        top = top
        bottom = bottom
    }
    , Cmd.batch [
        Cmd.map Top topCmd
        Cmd.map Bottom bottomCmd
    ]

Cmd.map is used to "elevate" the Counter message into the container type, using corresponding Top/Bottom case constructors as the mapping function. We batch the commands together to produce a single command for our entire container.

Note that even though we've implemented the counter as not issuing any commands, in a real application we still may want to map the commands to facilitate encapsulation - if at any point the child does emit some messages, we'll be in a position to handle them correctly.

And finally our update function:

let update msg model : Model * Cmd<Msg> =
  match msg with
  | Reset ->
    let top, topCmd = Counter.init()
    let bottom, bottomCmd = Counter.init()

    {
        top = top
        bottom = bottom
    }
    , Cmd.batch [
        Cmd.map Top topCmd
        Cmd.map Bottom bottomCmd
    ]

  | Top msg' ->
    let res, cmd = Counter.update msg' model.top

    { model with
        top = res
    }
    , Cmd.map Top cmd

  | Bottom msg' ->
    let res, cmd = Counter.update msg' model.bottom

    { model with
        bottom = res
    }
    , Cmd.map Bottom cmd

Here we see how pattern matching is used to extract counter message from Top and Bottom cases into msg' and it's routed to the appropriate child. And again, we map the command issued by the child back to the container Msg type.

This may seem like a lot of work, but what we've done is recruited the compiler to make sure that our parent-child relationship is correctly established!

And finally, we execute this as an Elmish program:

Program.mkProgram init update (fun model _ -> printf "%A\n" model)
|> Program.run