Search code examples
typeselm

Type inheritance in elm


What I am trying to achieve is type inheritance. What I mean by that is that I want to be able to have functions returning "sub-types" and then a function returning the "super-type". Let me give you an example

let's say I have a main View component which returns an Html Msg

view:  Model -> Html Msg
view model = 
    div [class "goal-box"] [
      function_a model,
      function_b model
    ]

Now I would like function_a and function_b to each be able to return a subtype of Msg

function_a: Model -> Html AMsg

function_b: Model -> Html BMsg

The reason why I want this is because I want to make sure function_a is limited in what kind of Msg it can produce, and same goes for function_b, but eventually I need a unified view that uses both.

So what came natural was to define Msg as

type Msg 
  = AMsg
  | BMsg

type AMsg
  = AnotherMsg Int
  | AgainMsg Int

type BMsg
  = ThisMsg String
  | ThatMsg Int

This does not seem to work, as the compiler tells me that it was expecting a return value of type Html Msg and not Html AMsg.

I hope this is clear. I feel like types are the concept I am struggling with the most coming from JS, but hopefully I am headed in the right direction.

DISCLAIMER

I have asked a similar question earlier in the day but I realized I made a mistake and then confused myself a couple of times as I was editing it. So I had to delete it. Apologies in advance to the people that took the time to read it and answer.


Solution

  • There are two main issues here.

    Firstly AMsg and BMsg in your Msg do not refer to those types, they are just constructors for your Msg type.

    You need to change this to:

    type Msg 
      = AMsg AMsg
      | BMsg BMsg
    

    Here first AMsg and BMsg on each line are constructors for Msg type, and second ones refer to your other types. After this you can create values like AMsg (AnotherMsg 34).

    Secondly you need to use function Html.map in your view to change message types so that when e.g. function_a sends message AnotherMsg 34 (of type AMsg), that will be transformed into AMsg (AnotherMsg 34) (of type Msg) and so in your view all messages are of same type.

    Full example code below, with ellie example here: https://ellie-app.com/3TG62zDLvwFa1

    module Main exposing (main)
    
    import Browser
    import Html exposing (Html, button, div, text)
    import Html.Events exposing (onClick)
    
    type alias Model =
        {}
    
    init : Model
    init =
        {}
    
    type Msg 
      = AMsg AMsg
      | BMsg BMsg
    
    type AMsg
      = AnotherMsg Int
      | AgainMsg Int
    
    type BMsg
      = ThisMsg String
      | ThatMsg Int
    
    view : Model -> Html Msg
    view model = 
        div [] [
          Html.map AMsg (function_a model),
          Html.map BMsg (function_b model)
        ]
    
    function_a : Model -> Html AMsg
    function_a model =
        div [] [ text "A" ]
    
    function_b : Model -> Html BMsg
    function_b model =
        div [] [ text "B" ]
    
    update : Msg -> Model -> Model
    update msg model =
        model
    
    main : Program () Model Msg
    main =
        Browser.sandbox
            { init = init
            , view = view
            , update = update
            }