The “I’m stupid” elm language nugget #4:
This is going to be a 3 part series in order both to keep the blog-like momentum and allow me not to write a novel in one post.
Sum types are inheritance. Type erasure is polymorphism. Data are objects. And yes, I just used “data” as the plural of datum :-)
I’ve wanted to write about the equivalence between runtime polymorphism in OO languages and sum types in functional languages, but didn’t think I had much to contribute. Until now, I’ve been using elm for a couple of parts of an app that requires fairly complicated markup generation and the ability to mix dragging, scrolling and reordering of submodels. So far, each view contained a uniform submodel type so while I had some intuition about how a more generic model container would work in elm (and about the topic of this post in a nutshell), I never actually wrote the code to make it possible.
Now I’m converting the app to an SPA to more easily support some design changes, so the first thing I had to do was take the formerly top-level elements I had individually hosted in embeds into an SPA. I also wanted nice animation between views, which basically comes for free in the SPA model.
I started by hosting a couple of views. They both use the obvious elm idioms of create/update/view with exported Action and Model types, and of course they each use their own unrelated types. This gave me a great way to dramatize the connection in a way that can be felt pretty viscerally as well as ending with some nicer code (although not what I’d call great quite yet).
All views are in an Array SubModel in the parent model, and of course, SubModel is a sum type containing the various descendants, or child types as it were. SubModel in other words is providing an interface of sorts (although a crappy one at the moment) that erases the descendant types. Note that I’m using interface here not in the formal sense of Java and C#, but in the type-standin generalism used by golang for example. The uses are compatible as golang interface is simply an automatic, duck-typed way of generating the code that glues interfaces to concrete types in Java and C# without the programmer having to specifically declare a desire for it. In most functional languages, you must write some of this yourself.
So initially, I had this:
In other words, not good, but I started here to make the repetition obvious and hopefully give myself a hint about the actual meat of converting this to something more abstract.
It should be obvious that this is non-optimal. Despite that we’re storing the submodels in a container, we’re dragging our feet about treating them as the same kind of object.
I tried a Bob Martin style refactoring session, which is often how I do these things. If you’re not familiar, Bob Martin, sometimes called “Uncle Bob” is a notable elder statesman of software, the originator of SOLID, proponent of TDD and an all around smart person. Often I’ll want to do a refactor on some code, and will try it optimistically, by which I mean that I’ll start from the ends (either changing a type or argument list at the top or bottom of the hierarchy) and see what type errors fall out and fix them in the intermediate layers. This is often a quick way of refactoring code, but sometimes the types are complicated, or an idea that is more intuition than reasoning is exposed as not being fully enough baked. In either case, if I spend more than 15–30 minutes fighting with it, I do it Bob Martin style; do the smallest possible changes, do them one at a time, compile, test, commit after each one. This works when other attempts don’t.
The end goal will have generic code pass a type erased function to some middleware. The type erased function must do this eventually:
Harmlessly return nonmatching submodels.
When the type of the model and action match, apply the action to the model using a type specific update function, yielding model, effects in that type.
Maps the model and effects back to the type erased form via type-specific functions.
In part 2, I will detail each transformation that turned this flatly nasty first draft into something that allowed this code to think of the contained subviews as objects of the same type.