4
votes

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.

1
I think what you're trying to do, and the way you tried to do it in the previous question, makes perfect sense and is a feature in OCaml called polymorphic variants. Unfortunately Elm doesn't support it, and that might be because there's some subtle issues with this kind of thing that makes it less appealing, especially for a language aimed at beginners.glennsl
@glennsl yeah, I think that’s what you were trying to explain to me in my other question. Unfortunately I phrased it wrong and I had a couple of other errors that made me believe inheritance was working correctly. I thought about editing the question but it was getting too confusing so I decided to just delete and recreate. Anyhow, Markus answer seems to get the job done, but I certainly wish this was a bit more straight forward. It might happen in the future, we are only at version 0.19 after allNicola Pedretti

1 Answers

6
votes

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
        }