Hi everyone!
I recently had an idea. This is by no means a revolutionary concep, and other people probably have thought about it before (but maybe not in the context of Elixir), but I think it might be a useful one.
I have not gotten around to (attempt to) implement it yet. I do think it is possible (and of ‘average’ implementation difficulty. Not trivial, but not ridiculously hard either), but I would like to give the community the opportunity to shoot holes in the idea, as well as gauge if I am the only one excited about this, or if other people would like to use it as well.
Total Case Statements
It is very frequent that we pattern-match in a case-statement or function-head on the different possibilities a value might take. Of course, since Elixir is a dynamically/unityped language, a value might be ‘anything’ at any given time, but usually we expect only one of a small subset of these values, especially when we are using a(n approximation of a) sum type. Examples of those are for instance:
-
true | false
, [] | [head | tail]
:lt | :gt | :eq
-
{:ok, value} | {:error, problem}
-tuples, Ecto.action() :: nil | :insert | :update | :delete | :replace | :ignore
- The atom-names of the fields in your struct.
etc.
Now, it would be nice if we can somehow indicate that there is a case-statement where we expect such a sum type, and we want to make sure that we have cases for all possible values of that type. In a statically-typed language, this is usually built-in. In a dynamic language, it is not. But if we indicate to the case-statement the typespec that we want to handle, then it could be able to check if we have implemented clauses for each of the inhabitants of the type.
So I propose a macro, with a signature like e.g. total_case typespec, value do ... end
, which could be implemented in a library, which would:
- Check, at compile-time, if the different match-handlers in the block together cover all possibilities of the type that is passed in.
- Maybe (if possible and not ridiculously hard to implement) warn about clauses that prevent later clauses from ever matching.
- After this check, compile down to a simple
case value do ... end
.
Sounds this useful to anyone?
The main thing that I don’t know about currently (besides having not enough spare time to start working on this right away) is how easy it is to programmatically interpret typespecs at compile-time.
It might also be possible that Dialyzer/Dialyxir already does some case-checking; this is something I don’t know, since until today I have struggled to set up + configure Dialyzer properly for my code. But even then I think that having a macro that would enforce this behaviour once set up would make a lot of sense over/alongside using an opt-in typechecker.
What do you think?