I am having a hard time thinking of a real life application with as few as three widgets, so the answer to the question as stated might be useless in the long run. At the same time, I think it is a very good question if considered in a wider sense. Looking into cases like this is an opportunity to practise in program construction and requirements discovery. But this means that we have to ask questions to the question itself.
There are 3 actions. All need to be performed. Based on the value of a variable, one of the action needs to be performed slightly differently. It needs to be substituted with a variant.
You think this is a simple statement of a simple problem. But it is not really. There are many ways to look at this description. For one, we may ask: what do we already know at compile time and what do we know only at run time? Or we may ask: are there really only 3 actions or are there actually 6 of them? Or even more?
Let us look at the first question. I am going to assume that we know at compile time what exact actions need to be performed in each of 3 cases. You correctly identify that a sum type of 3 values is an input, and the output is the execution of all actions in no particular order. This means that the type of the function we are looking for is ∀m. Applicative m ⇒ Var → m ( )
. (I am choosing a constraint weaker than Monad
to emphasize that all component actions are independently determined ahead of execution.) So, we want a function that chooses one of three possible composite actions.
The second question is now easy to answer. Saying that there are three component actions, each of which may be performed slightly differently, is of course a misnomer. This really means that there are 6 different component actions to choose from. But we are only interested in three specific combinations of these component actions — this is really specific! We would like to disallow any other combination. And there are 216 combinations of three from six, so we only allow about 1% of all possibilities.
This is where a Haskell programmer becomes suspicious. Like, can I actually stack the deck so that only these desirable possibilities are allowed? Observe that nothing prevents you from accidentally writing your code as:
data Var = A | B | C
case var of
A -> do_A2
_ -> do_B1
case var of
B -> do_A2
_ -> do_B1
case var of
A -> do_C2
_ -> do_C1
It is not immediate to spot the error (there are actually two). You are not protected by the type system in any way. So, you are right in judging that this code is not good enough!
Unfortunately, it is not trivial to design the types in such a way that correct code writes itself. I outlined some possible approaches above, such as using a zipper or a generalized algebraic type. Consider also that there is no decidable equality on IO
actions — so we cannot really say that do_C2
is any different from do_A2
and so on. In the end, there has to be some code that defines these actions. The best we can do is make sure they are defined once and in the simplest possible way, so that the possibility of error is reduced. We can then add some phantom types to these component actions, so that we can distinguish actions that create editable and non-editable widgets on type level. Then we would construct a type that can only hold one editable widget at once, and three widgets total. We can also try to make sure that an editable widget of type A
cannot become a non-editable widget of type B
, and this refers us back to the question of how state transitions proceed. It would be interesting to see how far this project can be carried.
If you only need three widgets once and forever, the code you started with is fine — advanced type machinery begins to pay off at a larger scale, maybe at dozens of widgets, and over time. But, if your project is educational in nature, you may still want to put this effort in and try to do it now!