xamarin.formsf#elm-architecture

Why is is the compiler telling me "Type misMatch for App message" when they are the same type


So, I've been fighting with the compiler on a type error.

This code was working as of a couple days ago.

Type misMatch for App level message

App.fs snippets

module App =
    type Msg =
        | ConnectionPageMsg of ConnectionPage.Msg
        | CodeGenPageMsg of CodeGenPage.Msg
//...
    let update (msg : Msg) (model : Model) =
        match msg with
        | ConnectionPageMsg msg ->
            let m, cmd = ConnectionPage.update msg model.ConnectionPageModel
            { model with ConnectionPageModel = m }, cmd
        | CodeGenPageMsg msg ->
            let m, cmd = CodeGenPage.update msg model.CodeGenPageModel
            { model with CodeGenPageModel = m }, cmd
//...
    let runner =
        Program.mkProgram init update view
        |> Program.withConsoleTrace
        |> XamarinFormsProgram.run app

I've added explicit aliases and the original error :

Type mismatch. Expecting a
    'App.Msg -> App.Model -> App.Model * Cmd<App.Msg>'    
but given a
    'App.Msg -> App.Model -> App.Model * Cmd<Msg>'    
The type 'App.Msg' does not match the type 'Msg'

Became these:

App.fs(50,50): Error FS0001: The type 'PocoGen.Page.ConnectionPage.Msg' does not match the type 'PocoGen.Page.CodeGenPage.Msg' (FS0001) (PocoGen)
App.fs(32,32): Error FS0001: Type mismatch. 
Expecting a 'App.Msg -> App.Model -> App.Model * Cmd<App.Msg>'    
but given a 'App.Msg -> App.Model -> App.Model * Cmd<Msg>'    
The type 'App.Msg' does not match the type 'Msg' (FS0001) (PocoGen)

Other remarks

Right before these errors started appearing I was working on converting a blocking syncronous call to a async command in the ConnectionTestPage and removed the calling code for the cmd hoping that would fix it. (It did not)

ConnectionPage.fs Messages

type Msg =
    | UpdateConnectionStringValue of string
    | UpdateConnectionStringName of string
    | TestConnection
    | TestConnectionComplete of Model
    | SaveConnectionString of ConnectionStringItem
    | UpdateOutput of string

ConnectionPage.fs update

let update (msg : Msg) (m : Model) : Model * Cmd<Msg> =
    match msg with
    | UpdateConnectionStringValue conStringVal ->
        { m with
              ConnectionString =
                  { Id = m.ConnectionString.Id
                    Name = m.ConnectionString.Name
                    Value = conStringVal }
              CurrentFormState =
                  match hasRequredSaveFields m.ConnectionString with
                  | false -> MissingConnStrValue
                  | _ -> Valid }, Cmd.none

    | UpdateConnectionStringName conStringName ->
        { m with
              ConnectionString =
                  { Id = m.ConnectionString.Id
                    Name = conStringName
                    Value = m.ConnectionString.Value }
              CurrentFormState =
                  match hasRequredSaveFields m.ConnectionString with
                  | false -> MissingConnStrValue
                  | _ -> Valid }, Cmd.none

    | UpdateOutput output -> { m with Output = output }, Cmd.none
    | TestConnection -> m, Cmd.none
    | TestConnectionComplete testResult -> { m with Output = testResult.Output + "\r\n" }, Cmd.none
    | SaveConnectionString(_) -> saveConnection m, Cmd.none

I've played with the Fsharp Version (because incidentally I did update to 4.7.2 a bit before getting this error)

The Full Repo:

https://github.com/musicm122/PocoGen_Fsharp/tree/master/PocoGen


Solution

  • The two branches of the match inside App.update have different types. The first branch has type App.Model * Cmd<ConnectionPage.Msg> and the second page has type App.Model * Cmd<CodeGenPage.Msg>.

    You can't generally do that. This, for example, wouldn't compile:

    let x = 
        match y with
        | true -> 42
        | false -> "foo"
    

    What type is x here? Is it int or is it string? Doesn't compute. A match expression has to have all branches of the same type.


    To convert Cmd<ConnectionPage.Msg> into a Cmd<App.Msg> (by wrapping the message in ConnectionPageMsg) you can use Cmd.map:

    let update (msg : Msg) (model : Model) =
        match msg with
        | ConnectionPageMsg msg ->
            let m, cmd = ConnectionPage.update msg model.ConnectionPageModel
            { model with ConnectionPageModel = m }, Cmd.map ConnectionPageMsg cmd
        | CodeGenPageMsg msg ->
            let m, cmd = CodeGenPage.update msg model.CodeGenPageModel
            { model with CodeGenPageModel = m }, Cmd.map CodeGenPageMsg cmd