To add to this sentiment:
I love breaking up conditionals into function clauses, it’s my favorite feature of the BEAM.
The reason why is that it lets me separate requisite expectations from navigable ones at a syntax level, rather than a semantic level.
A strict function clause declares, “given these conditions, I can try to do this work”.
Scrolling through all function heads for a function lets you know what work it can attempt. Reading through each function clause implementation lets you know how it tries to do that work, and how it handles failure if it cannot proceed. But you know the work not specified in a function head will never be attempted.
By this compass, the guiding philosophy of “let it crash” is illuminated: don’t attempt anything you already know you can’t handle; instead, immediately let the caller know it asked for an invalid operation so it can decide how to recover. Promoting requirements into pattern-matched function heads and not providing fallbacks is just a clean way of declaring what you are willing to handle–versus everything else you are not willing to, and let crash (and recover to a known good state).
In this example, you know ahead of time which suits you are able to handle:
"Diamonds"). Similarly with cards of rank
case statement navigates what you know you are able to handle, and can provide a fallback. But in this situation you already know there is no reasonable fallback: when a caller asks for a card of suit
9001, let it crash.
- When a caller asks for a card of a rank outside of
0..12, let it crash.
- When a caller asks for a Queen of Hearts, but it’s already been dealt… then maybe handle the situation by telling it to draw again.
The difference between the former and the latter is that in the former, the caller is asking for something so impossible given business requirements, you know it must be in a bad state, and should rethink its understanding of the world; in the latter, it is trying its best but just slightly misinformed about the current state of play.
“Let it crash” is about separating the former from the latter, and function heads are an elegant boundary to differentiate between them.
case statements in functions work as well, but are under-utilizing the expressiveness of the BEAM.
Notably, you can detect issues with the former request without consulting a database of dealt cards–and choosing to do so as early as possible improves the speed of your program, as well as understandability of it. Moving this sort of insight into a function head just helps communicate intent to maintainers quicker, as well as possibly improving the runtime of your program.