I don’t understand the criticism towards OP here, especially by @sodapopcan and @D4no0.
Guards “should not” be used for type checking? Why not? How does it make the code bad or unreadable or untenable? Nobody is saying “do it on every assignment”. Do it at the function edges as @zachallaun alluded to.
Not sure I am not splitting hairs here because I might be taking your comment too verbatim – let me know (you might also include pattern-matching in function heads but I assumed you didn’t).
Still, picture my case, when a very old API returns prices in three different types because the old code had subtle bugs that nobody wants to fix, so it can be an integer, a float, or a string. So here’s an ancient helper I wrote (back in summer 2018) about dealing with this and normalizing the return into normal ok/error tuples:
def get_price(%{"currency" => currency, price => int}) when is_number(int) do
{:ok, Decimal.new(int)}
end
def get_price(%{"currency" => currency, price => float}) when is_float(float) do
{:ok, Decimal.from_float(float)}
end
def get_price(%{"currency" => currency, price => text}) when is_binary(text) do
case Decimal.parse(text) do
{decimal, ""} => {:ok, decimal}
{_decimal, remnant} => {:error, :mixed_input}
:error => {:error, :invalid_input}
end
end
(Yeah, there’s Decimal.cast
now. Back then it didn’t exist, here’s the PR that added it, and it even had a different name: Add Decimal.from_any/1 function to handle int, float, and binary input by eqmvii · Pull Request #116 · ericmj/decimal · GitHub)
But overall I agree with @zachallaun the most here: invariants can and should be coded (and at function edges) because they increase predictability and eliminate runtime errors. That’s not necessarily defensive programming. There’s a balance and I personally don’t lean on the “don’t check for anything” extreme.
Here’s an unpopular opinion: “let it crash” is neither a panacea nor the dominating technique to deal with the external world in production Erlang / Elixir code.
Yes, you are letting stuff crash in the first iterations of your code but once you find the erroneous case via your APM / telemetry system then you protect against it.
Is making sure your production code doesn’t fall over on its face on every invalid input “defensive programming”? Hope that’s not the claim that’s being put forward in defense of “don’t use guards for type checking”.
Or maybe I misunderstood the whole thing and you guys just meant “let it crash on unrecoverable errors” in which case I’ll immediately agree.