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 value
is :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 (enum State
):
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
Without 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.