I am currently reading Programming Elixir and I am doing one of the exercises where you should write a solution to the “I’m thinking of a number between 1 and 1000”
I have made it work, but I wanted to add a simple guard clause: I want to check if the passed number is actually in the passed range. As far as I have read only handful of functions and operators are allowed in guard clauses, but the ‘in’ is one of them.
So 1st I tried this:
def guess(actual, range) when (actual not in range), do: IO.puts "actual #{actual} is not in #{range}"
But I got: (ArgumentError) invalid args for operator “in”, it expects a compile-time proper list or compile-time range on the right side.
2nd I tried to make a list from the range:
def guess(actual, range) when (actual not in (Enum.to_list range), do: IO.puts "actual #{actual} is not in #{range}
And again I got the same error, this time saying that I the right side is the function itself instead the evaluation of it, which should be proper list ?!?
But this works(when the range is defined in the guard clause) :
def guess(actual, min..max) when actual < min or actual > max
Guards are very limited, and much of the fancy properties of in in guards is done at compile time using macros; because of this a compile time range literal can be used, but a runtime range value cannot.
Well, at the point, where you already matched on min and max, and you can make it a compiletime range again for the guard:
iex(1)> defmodule M do
...(1)> def guess(n, min..max) when n in min..max, do: "Yeah, you won!"
...(1)> def guess(_, _), do: "There is no try!"
...(1)> end
iex(2)> M.guess 4, 1..1000
"Yeah, you won!"
iex(3)> M.guess 4, 1..3
"There is no try!"
I consider this slightly more readable than n > min and n < max (which I generally would prefer to write as min < n and n < max.
Also I just realise, that there is a very big difference between using comperators and using in range:
iex(1)> defmodule M do
...(1)> def f(n, a..b) when n in a..b, do: "Yeah!"
...(1)>
...(1)> def g(n, a..b) when a <= n and n <= b, do: "Yeah"
...(1)> def g(_, _), do: "WTF?"
...(1)> end
iex(2)> M.f(5, 10..1)
"Yeah!"
iex(3)> M.g(5, 10..1)
"WTF?"
Sounds like a good use for a defguard (especially with OTP21’s new map_get guards as then you could make a in_range/2, but otherwise an in_range/3 would work regardless, though an in-range/2 could be made if my original defguard proposal was made instead…).