ollien
ollien

Reputation: 4766

Why does range equality not work in a guard?

I want to write a function that will, given a range and a value, n, check if the range spans exactly n..n. My first attempt looked something like this, but does not have the expected behavior (note the first return value is false)

defmodule Range do
  def equal?(n, range) when range == n..n, do: true
  def equal?(n, range), do: false
end

IO.puts(Range.equal?(1, 1..1)) # false
IO.puts(Range.equal?(1, 1..5)) # false

However, if I shift the approach to use pattern matching on the range argument, I can produce a function that works.

defmodule Range do
  def equal?(n, n..n), do: true
  def equal?(n, range), do: false
end

IO.puts(Range.equal?(1, 1..1)) # true 
IO.puts(Range.equal?(1, 1..5)) # false

Looking at the two, it's not clear to me why they are not equivalent. If I replace the when range == n..n with when range == 1..1 this works as expected (obviously only for n = 1).

Why is this?

Upvotes: 3

Views: 137

Answers (1)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

This is a bug in core.

Within regular context, unless explicitly specified via //step, the step for the Range struct is explicitly set to 1, while inside a guard it’s set to nil: https://github.com/elixir-lang/elixir/blob/v1.13.4/lib/elixir/lib/kernel.ex#L3805-L3818

defmodule R do
  def equal1?(n, range) when range == %Range{first: n, last: n, step: 1}, do: true
  def equal1?(_n, _range), do: false

  def equal2?(n, range) when range == n..n//1, do: true
  def equal2?(_n, _range), do: false
  
  def equal3?(n, range) when range == n..n, do: true
  def equal3?(_n, _range), do: false
end

iex|💧|1 ▸ R.equal1? 1, 1..1
true
iex|💧|2 ▸ R.equal2? 1, 1..1
true
iex|💧|3 ▸ R.equal3? 1, 1..1
false

I’m going to provide a pull request fixing this, thanks for reporting.

Unfortunately, for dynamic first and last values there is no way to infer a proper step (while it’s still possible for same values, or one might blindly infer 1, both solutions are incomplete and hacky.)

That said, please use a well-formed modern range with a step n..n//1 to make it work in guards as expected.

Upvotes: 5

Related Questions