Pattern matching on similar types

In regard to making impossible states impossible, I often find myself using a pattern similar to this one:

type Content
    = ContentString (Values String)
    | ContentListString (Values (List String))
    | ContentFloat (Values Float)
    | ContentList (Values (List Content))

type alias Values a =
    { name : String
    , values : List a

where I’m certain about the fact that each Content have some associated Values. The only thing that changes is the concrete type of the said Values.

I want to keep the compile-time property that I cannot associate a ContentListString with anything but a List String as I don’t want to manage those cases at runtime.

But the problem I have with this pattern is that I need to pattern match against all cases each time I want to do something on a Content, even though I’m not using anything but the name of the Content during said operation.

I always can make specialized functions that does everything I need, but those functions each does a pattern match without any kind of abstraction, and that is very error-prone and difficult to refactor in my opinion.

Is there something I’m missing out to manage those kind of patterns?
Are there any better options to express such things?

Thank you for your time!

Personally I’d go with a name :: Content -> String function but you can engage in the “algebra” of algebraic types.
Every case of your content-type has some name so you can use the distributive law and factor it out:

type ContentValues
    = ContentStrings (List String)
    | ContentListStrings (List (List String))
    | ContentFloats (List Float)
    | ContentLists (List (List Content))

type alias Content = { name : String, values : ContentValues }

this way you can factor out the common values and access them easier - downside is of course that the values themselves are now nested one level deeper (inside another record)

Wow, thank you!

That’s nice we can think about types in term of factoring them out!

I think I’ll go with the name :: Content -> String function for now, as it allows me to not rewrite my whole program for now
I guess having a function for each generic field is OK as it is

But I’ll try to factor out all common denominator next time, and put only specific things in the Contentvalues box!

However, how do these solutions compare in term of performance? Are they both equivalent? It seems to me that the getName path is slower, as it needs to check for each constructors at runtime, which is not the same as simply applying .name on a Content, but is it really true? Is there some compiler magic in it? I know it’s peanut, we’re talking about Ο(1) anyway.
This is only a theoretical question though, as I’ll try to prefer what’s work best for clarity in each case

I don’t think you’ll notice much in performance (guess it depends on how big your Content-trees are :wink: )

Concerning the “algebra” stuff - this is surprisingly deep (there is even a calculus side where the derivative of your type yields the corresponding zipper-structure). There are a few nice articles/posts on this out there (don’t know one for Elm but I guess there could be one also) and I like this one in case your are interested in learning more.

To be fair: Aside from simple transformations like this one here (which you could see with out the algebra stuff) I don’t really use this - but it’s nice to know.

1 Like

OK, this blew my mind

I love this interpretation of derivatives, that’s awesome!

Thank you!

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.