Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to do pattern matching on map keys in function heads in elixir

I can't seem to find a way to pattern match on a map's key in a function head. Is there a way to do this? What I'm trying to do is run different code depending on whether a certain key already exists in a map or not (and also wanted to avoid if/else and the like)

This is what my code looks like

def my_func(key, %{key => _} = map), do: ...

which gives me this error

** (CompileError) illegal use of variable key inside map key match, maps can only match on existing variable by using ^key

Of course I also tried it using the ^

def my_func(key, %{^key => _} = map), do: ...

which then gives

** (CompileError) unbound variable ^key

I'm using elixir 1.3.1/erlang 19.0 x64 on a windows 8.1 machine. Thanks for reading!

like image 321
bottlenecked Avatar asked Jul 06 '16 09:07

bottlenecked


People also ask

What is pattern matching in Elixir?

Pattern matching allows developers to easily destructure data types such as tuples and lists. As we will see in the following chapters, it is one of the foundations of recursion in Elixir and applies to other types as well, like maps and binaries.

What is lot pattern matching?

Pattern matching is the act of checking one or more inputs against a pre-defined pattern and seeing if they match. In Elm, there's only a fixed set of patterns we can match against, so pattern matching has limited application.

Is map key Elixir?

12.3) Maps are the "go to" key-value data structure in Elixir. Key-value pairs in a map do not follow any order (that's why the printed map in the example above has a different order than the map that was created).

What is pattern matching in programming?

Pattern matching in computer science is the checking and locating of specific sequences of data of some pattern among raw data or a sequence of tokens. Unlike pattern recognition, the match has to be exact in the case of pattern matching.


1 Answers

You can try several approaches depending on the specific problem and the Elixir version you're using:

  • Map pattern matching
  • Checking a given key exists (Elixir < 1.10)
  • Checking a given key exists (Elixir >= 1.10)

Map pattern matching

If you can just pattern match with the key you need:

defmodule Test do
  def my_func(%{"a" => value}), do: {:a, value}
  def my_func(%{"b" => value}), do: {:b, value}
  def my_func(_), do: :error
end

Then in IEx:

iex(1)> Test.my_func(%{"a" => 1})
{:a, 1}
iex(2)> Test.my_func(%{"b" => 2})
{:b, 2}

Also order of the clauses are important e.g. if you are trying to match %{"b" => 2} but you have the following map %{"a" => 1, "b" => 2}, the key "a" will match first, because is in the first clause:

iex(3)> Test.my_func(%{"a" => 1, "b" => 2})
{:a, 1}

If you want to generate something for every key you can match, I recommend a different approach. For example, if you want to map a function to those keys:

defmodule Test0 do
  def my_op({"times_2", value}), do: {"times_2", value * 2}
  def my_op({"times_3", value}), do: {"times_3", value * 3}
  def my_op({key, value}), do: {key, value}

  def my_func(m) do
    Enum.map(m, &my_op/1) |> Enum.into(%{})
  end
end

So you would get the following:

iex(1)> Test0.my_func(%{"times_2" => 2, "times_3" => 3, "whatever" => 42})
%{"times_2" => 4, "times_3" => 9, "whatever" => 42}

Checking a given key exists (Elixir < 1.10)

You cannot pattern match a key to a variable. The problem is the compiler needs to generate code to search for something it doesn't know yet. When you pattern match you usually give the compiler some hints of what it'll receive. For keys in maps the hint is the key itself. In this case, even pointing that the first argument should be the key to look for, it is not enough for the compiler. So your approach should be to use an if statement:

defmodule Test1 do
  def my_func(k, m) do
    if Map.has_key?(k, m) do
      ... do something when key is found ...
    else
      ... do something when key is not found ...
    end
  end
end

Checking a given key exists (Elixir >= 1.10)

Elixir 1.10 adds the guard is_map_key/2 that can be used to solve this problem finally!

defmodule Test2 do
  def my_func(key, map) when is_map_key(map, key) do
    ... key is found ...
  end

  def my_func(_, _) do
    ... key is not found ...
  end
end

This will definitely solve the compilation issue :)

I hope this answers your question.

like image 53
Alex de Sousa Avatar answered Oct 20 '22 06:10

Alex de Sousa