Reputation: 4766
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
Reputation: 121010
This is a bug in elixir 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