Suppose you want to choose a code path depending on a variable value. You could use a case for that:
case value when :one then p one: value when :two then p two: value end
This has several problems, one of which is this: what’s gonna happen if
:three? Easy, if a blanket
else path is available, hard otherwise.
Some other languages have facilities to check that every possible value has a code path so that no unexpected implications have a chance to occur, but Crystal doesn’t.
We could use a different approach. Since Crystal doesn’t allow to overload methods on constants (
8 | def state(s : State::One) : Nil ^--------- Error: State::One is not a type, it's a constant
We’ll have to instead use honest types:
abstract struct State struct One < State; end struct Two < State; end struct Th3 < State; end end def state(s : State::One) : Nil; p one: s end def state(s : State::Two) : Nil; p two: s end def state(s : State::Th3) : Nil; p th3: s end state (rand > 0.5) ? State::One.new : State::Two.new
state(State::Th3) the compiler would report an error:
13 | state (rand > 0.5) ? State::One.new : State::Two.new ^---- Error: no overload matches 'state' with type State+ Overloads are: - state(s : State::One) - state(s : State::Two) Couldn't find overloads for these types: - state(s : State::Th3)
This works because even though the temp variable should be
State::One | State::Two, it’s actually a
State+ because of the subtype folding optimisation.
This way you lose some of the enums sugar, which you can then manually add back with some macros and whatnot, but what you gain is built-in future proofing for adding new values.