Search code examples
typeselmalgebraic-data-typesunificationparametric-polymorphism

How do I unwrap a generic type alias from a union type which makes the type alias more specific?


I have type Model, which describes two possible states of generic type alias ModelFields. And I want to extract generic ModelFields record from an instance of the Model type.

type Model endValue stats
  = ShowEndValues (ModelFields Organism endValue)
  | ShowStatistics (ModelFields Rank stats)

type alias ModelFields object results =
  { results : List results
  , objects : List object
  , cellValue : results -> String
  , location : Maybe (Rank, Rank)
  }

getModelFields : Model endValue stats -> ModelFields object results
getModelFields model =
  case model of
    ShowEndValues modelFields ->
      modelFields 
    ShowStatistics modelFields ->
      modelFields

But Elm does not allow it saying for each case expression that

TYPE MISMATCH - Something is off with the 1st branch of this `case` expression:

55|       modelFields 
          #^^^^^^^^^^^#
This `modelFields` value is a:

    ModelFields #Organism# #endValue#

But the type annotation on `getModelFields` says it should be:

    ModelFields #object# #results#

#Hint#: Your type annotation uses type variable `object` which means ANY type of
value can flow through, but your code is saying it specifically wants a
`Organism` value. Maybe change your type annotation to be more specific? Maybe
change the code to be more general?

So my question is: how do I get ModelFields from Model? Or am I doing something fundamentally flawed?

UPD. Details of what I'm trying to model.

I have objects of type Organism. They are grouped into Ranks. My server does some pairwise analyses of Organisms, i.g. calculates Similarity and Distance between two Organisms. I'd like to display the results of these analyses in two tables on different pages, one page for Similarity analysis, and one page for Distance analysis. This means the table should be reusable to accept analysis results of any form. On the other hand, there is one common pattern for these tables. They can be in two states:

  1. Showing concrete results of pairwise comparisons (endValue in Model) between Organisms, so the table's rows and columns represent Organisms.
  2. Showing statistical values (e.g. mean or standard deviation, stats in Model) between groups (Ranks) of Organisms, in this case the table's rows and columns represent Ranks.

Which Ranks or Organisms are shown depends on location field of ModelFields. User can click a button and I'd like to change the location in my update function. Potentially this navigation change can switch between ShowEndValues and ShowStatistics states. That's why I'm trying to unwrap ModelFields from Model constructors. I'm attaching a simple illustration in hope it helps to clarify.table illustration


Solution

  • This is indeed not possible.

    The model value in the getModelFields function will have type Model endValue stats. This means that it will either be ShowEndValues variant, containing a value of type ModelFields Organism endValue, or the ShowStatistics variant containing a value typed ModelFields Rank stats. The each branch of the case expression unpacks one variant and outputs the modelFields value.

    Now, let’s try to determine the type of the case expression. We look at the types returned by each branch and try to find a type that encompasses both types (unify them). On a first sight, it looks promising: both values are in the form ModelFields a b so we will recursively try to unify each of the child types. Here we come across a problem – a of the first type has type Organism whereas the second one is Rank. There is not type that is both Organism and Rank so the compilation fails.

    Note: As you can see from the error message, Elm actually tries to unify the types of the branches with the function result type. (I described the other direction because I think it is clearer to understand.) The direction followed by Elm also fails because it will recurse and try to unify a concrete Organism type with a generic object type, similarly to how we tried to unify two concrete types.