Elixir: Pattern matching error when the same variable is used twice

Created on 26 Apr 2020  路  6Comments  路  Source: elixir-lang/elixir

Environment

  • Elixir versions:

    • 1.6.6

    • 1.7.4

    • 1.10.3

  • Erlang/OTP 21 [erts-10.3.4] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]
  • Operating system: MacOS 10.15.4 (19E287)

Explaning the code

defmodule Furniture do
  def item(:chair, %{seat_height: h, width: h, length: l}) do
    "Chair with seat height: #{h}, width: #{h}, length: #{l}"
  end
end

See that in the second argument (a map), you can see that seat_height and width sends the value to the same variable, which is an error.

The problem is that no error is raised during compilation time, but running time.

Current behavior

During compilation time, no error is thrown and you will get an error when try to run the function

iex(1)> defmodule Furniture do
...(1)>   def item(:chair, %{seat_height: h, width: h, length: l}) do
...(1)>     "Chair with seat height: #{h}, width: #{h}, length: #{l}"
...(1)>   end
...(1)> end
{:module, Furniture,
 <<70, 79, 82, 49, 0, 0, 5, 228, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 203,
   0, 0, 0, 21, 16, 69, 108, 105, 120, 105, 114, 46, 70, 117, 114, 110, 105,
   116, 117, 114, 101, 8, 95, 95, 105, 110, 102, ...>>, {:item, 2}}
iex(2)>
nil
iex(3)> Furniture.item(:chair, %{seat_height: 45, width: 40, length: 40})
** (FunctionClauseError) no function clause matching in Furniture.item/2

    The following arguments were given to Furniture.item/2:

        # 1
        :chair

        # 2
        %{length: 40, seat_height: 45, width: 40}

    iex:2: Furniture.item/2

This is an example without a function:

t = {1, 2} # {1, 2}
{a, b} = t # {1, 2}
a # 1
b # 2
{x, x} = t # ** (MatchError) no match of right hand side value: {1, 2}

Expected behavior

I think that will be better to recognise this and stop during compilation time. Not in execution time when it is called.

iex(1)> defmodule Furniture do
...(1)>   def item(:chair, %{seat_height: h, width: h, length: l}) do
...(1)>     "Chair with seat height: #{h}, width: #{h}, length: #{l}"
...(1)>   end
...(1)> end
** Some compilation error to avoid this kind of problem.

Most helpful comment

This won't be an error because it's an important feature of pattern matching, this allows us to write code like this:

def card_rank({x, x, x, x, _}), do: :four_of_a_kind
def card_rank({x, x, x, y, y}), do: :full_house
# etc

All 6 comments

This won't be an error because it's an important feature of pattern matching, this allows us to write code like this:

def card_rank({x, x, x, x, _}), do: :four_of_a_kind
def card_rank({x, x, x, y, y}), do: :full_house
# etc

Closing thanks to the explanation above.

I believe the following, using the pin operator, would be more explicit and, thus, preferrable:

def card_rank({x, ^x, ^x, ^x, _}), do: :four_of_a_kind
def card_rank({x, ^x, ^x, y, ^y}), do: :full_house
# etc

As the aforementioned would probably involve a sizeable amount of work to implement, requiring a naming convention could be a compromise, giving a warning otherwise, analogously to unused variables.

def card_rank({x, x, x, x, _}), do: :four_of_a_kind
# warning: variable "x" is reused in the same pattern match (if this is intentional, append the variable name with an exclamation mark)

def card_rank({x!, x!, x!, y!, y!}), do: :full_house
# no warning

I totally agree, @adrianomitre

^x means the previously bound variable. Using it as proposed would introduce left to right semantics to pattern matching, which we want to generally avoid.

!x could have been a good idea, but it introduces potential for confusion when mixed with ^x, plus it would be a very large change to make to the language, with probably hundreds of warnings in certain projects.

The pin operator allows us to access and "pin" the value the variable pointed to before the current expression. In a function head there is no previous value so the pin operator does not make sense in this context.

To quote the docs: "Accesses an already bound variable in match clauses", no variables are already bound before the function head so it does not apply.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cmeiklejohn picture cmeiklejohn  路  3Comments

lukaszsamson picture lukaszsamson  路  3Comments

eproxus picture eproxus  路  3Comments

GianFF picture GianFF  路  3Comments

ericmj picture ericmj  路  3Comments