When It Compiles, But Doesn't Work

What’s the difference between a violin and a vuvuzela? And what does that have to do with Elm? I wrote a post to explore that burning question in depth.


Hi Dillon, thanks for writing this up!

I totally get the point of your article, and it’s definitely a place where I want to be eventually. Still, I wonder if any of those practices are considered “premature abstractions” at the early stages of a project.

I’m (a beginner) currently migrating a React side project to Elm, and I tried applying some of those practices from the get-go. Still, I found that it was making things too complex for me to be productive, so I decided to get rid of those until I understood better the problem domain within the context of my new Elm codebase, but did I go too far? I can’t tell for sure!

I guess my question is, which of those practices make sense from the get-go, and which ones can wait.


I’m sorry to do this, Dillon, but I have to challenge your fundamental assumption that vuvuzelas can’t possibly make beautiful music: Vuvuzela Concert - YouTube

(I’ve just randomly seen this video a few hours ago, so it’s a funny coincidence to see your post. :smiley: I will read that blog post now, you have me hooked.)


That’s a great point @joanllenas! It’s a really interesting balance between guarantees and premature abstractions.

This conference talk is a nice take on that question:

And we explore that more in this podcast episode:

To me, the key is really to focus on the needs in your domain, and to build modules around that. I think that when writing code with types first it can be tempting to model the full type and build up a lot of constraints and logic all at once. So in my experience, it’s important to try to be incremental about building up types as well. Sometimes I’ll sketch out a type, then comment out some of the variants from my sketch that I don’t need to handle right away so I can keep them around for reference as I build the logic for the initial minimal code path.

One piece of advice I have is to “Make Impossible States Impossible” as part of the process when you find new constraints you want to enforce in your domain or if you find bugs. We’re only human so we won’t understand all the constraints right away, and some of them will evolve and change over time. And we will inevitably find bugs in our code. But when things do evolve or we do find bugs, always remember to look for ways to fix it through your types whenever possible.

I think it’s also something that you develop a sense for over time through experimenting and paying attention to what works. For example, I sometimes find that I want to build something in a module right away, and often tests are what tell me that. I was recently building some logic for input fields that can include interpolated values, and I immediately started with a unit test for them because I knew they had clear logic to test. When there’s a coherent and well-defined thing to test, I find that’s a clear sign that it belongs in a module, so in that case in my code it never exists outside of its own module. Actually, I start it directly in the test code, and then extract it out to a module as a refactoring step.

Anyway, that’s a lot of thoughts, but hope there are some useful tips in there! Happy coding!


I actually came across that video as I did some research on vuvuzela performances (never imagined I would write that sentence :laughing:).


Sometimes, rather than creating an opaque / custom type I like to just wrap things in a record with a named field. It does not required a lot more writing while keeping the code readable. For example: { project_uuid: String} -> Cmd Msg rather than String -> Cmd Msg. I picked this up from the data that comes from our python backend - python usually has no static types.


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