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) …
2 Likes

How about something like this?

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.

5 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