How to make impossible states impossible?
Wouter In t Velt

Nice article! A suggestion

Nice to have a problem that’s easy enough to explain but complex enough that the best solution isn’t obvious. Most real world problems are complex to solve and complex to explain, so not that great for general learning purposes :)

As I mentioned on Slack, it seems to me that your “4th version” is about organizing where the work is done rather than revising the model to deal with impossible states. I think it’s worth making that distinction because it’s easy to refactor in Elm when you aren’t changing the model, but in my experience it’s often tedious and painful if you are. (Which is not necessarily a bad thing, as it forces you to hammer out ambiguities in the state machine with the users/stakeholders, before you spend effort in implementing features in the wrong way or unnecessarily.)

Anyway, my point is, what are update helpers in version 3 and view helpers in version 4, could just as easily be extracted. Where the work gets done is quite fluid once you have the model right. So for instance:

setCountry : Country -> Destination -> Destination
setCountry newCountry destination =
case destination of
NotChosen ->
ToCountry newCountry

ToCountry oldCountry ->
ToCountry newCountry

ToCity oldCountry oldCity ->
if newCountry /= oldCountry then
ToCountry newCountry
ToCity oldCountry oldCity

Then your update becomes a one-liner:

update : Msg -> Model -> Model
update msg model =
case msg of
CountryPicked newCountry ->
{ model | destination = setCountry newCountry model.destination }

setCountry, setCity, etc. or similar are then like the basic API around your Destination type.

I often prefer this, but ultimately these kinds of decisions on “where to put the logic” seem quite different from decisions about how to structure your model to avoid impossible states — and not as important, because as you say it’s easy to refactor.

You might start with the logic in one place and then move it later when you have a new case where it makes sense to extract the functions or common parts of them, etc. But the same is not true when you need to make an underlying change to the way the data is modeled.

Anyway, it’s great to have articles like this, keep ’em coming!