In Elixir, to my understanding, explicitly writing raise(FunctionClauseError) is a huge code smell.
But is there any better way to write the following?
@spec calc(input :: pos_integer()) :: non_neg_integer()
# a base case
def calc(1), do: 0
# can't accept non-numbers and non-positive inputs
def calc(input) when not is_number(input), do: raise(FunctionClauseError)
def calc(input) when input <= 0, do: raise(FunctionClauseError)
# my computations, we can conceptually ignore what I'm doing here below
def calc(input) when (input &&& input - 1) == 0, do: :math.log2(input)
def calc(input) when Kernel.rem(input, 2) == 0, do: 1 + calc(Kernel.div(input, 2))
def calc(input), do: 1 + calc(3 * input + 1)
Without having to specify the first two conditions by hand, on each other clause, aka:
# here, I *must* write guards for each match, if I want to avoid Math errors!
def calc(input) when is_number(inpu) and input>0 and (input &&& input - 1) == 0, do: :math.log2(input)
def calc(input) when is_number(inpu) and input>0 and Kernel.rem(input, 2) == 0, do: 1 + calc(Kernel.div(input, 2))
def calc(input) when is_number(inpu) and input>0, do: 1 + calc(3 * input + 1)
To some extent I like raising FunctionClauseError because I clear the solution space beforehand, so I wonder what' the best call here.