# Can I check if value is in range in a guard clause

Hey,

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 test(x) when(x not in 1..100) …
``````
1 Like

``````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.

3 Likes

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`.

4 Likes

Ty, guys.

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?"
``````
1 Like

Nice catch, but this is actually a problem with the implementation of the solution using comparators:

This should work:

``````def g(n, a..b) when (a <= n and n <= b) or (a >= n and n >= b), do: "Yeah"
``````

Which is quite less readable and you have to remember to do it that way, while using the `n in a..b` approach just works.

3 Likes

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…).

1 Like