Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Shortcut for pattern matching on same value-key name in Elixir map

Tags:

I do a fair amount of pattern matching in this style:

def action(%{start_date: start_date, amount: amount, notify: notify %}) do
  # some action
end

Most of the times, the name I choose for the parameters are the same name in the map. Is there a shortcut of specifying that pattern matching case without repeating the same name for the key and the value ?

Something in the line of this pseudo code:

def action(%{start_date: %s, amount: %s, notify: %s}) do
  IO.inspect(start_date)
  # some action
end
like image 679
elpddev Avatar asked Jun 01 '17 06:06

elpddev


People also ask

What is pattern matching in Elixir?

Python and Elixir Programming Bundle Course Pattern matching is a technique which Elixir inherits form Erlang. It is a very powerful technique that allows us to extract simpler substructures from complicated data structures like lists, tuples, maps, etc. A match has 2 main parts, a left and a right side.

Are Elixir maps ordered?

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).

How do I update maps with Elixir?

Update a map But as with all values in Elixir, a map is immutable, so the result of the update is a new map. The simplest way to update a map is with this syntax: new_map = %{ old_map | key => value, ... } . However, this syntax won't add a new key to a map.


2 Answers

AFAIK, there is nothing out of the box, but one could simply create a macro for themselves to serve that purpose:

defmodule M do
  defmacro struct(params) do
    {:%{}, [], Enum.map(params, fn e -> {e, {e, [], Elixir}} end)}
  end
end

defmodule Test do
  require M # to use macro
  def action(M.struct([:a, :b]) = params),
    do: IO.inspect params, label: "Params are"
end

Test.action(%{a: 42, b: :ok})
#⇒ Params are: %{a: 42, b: :ok}
Test.action(%{a: 42})
** (FunctionClauseError) no function clause matching in Test.action/1

The code above is of course just an MCVE, you probably need to enhance it somehow to handle corner cases more gracefully (and, probably, to have the more explicit readable macro that behaves smarter than just spitting the AST out, and considers the binding, etc,) but I believe that explains the idea.

like image 113
Aleksei Matiushkin Avatar answered Oct 11 '22 17:10

Aleksei Matiushkin


I introduced a sigil, ~m{...}, to achieve destructing assignment.

~m{foo bar} = %{foo: 1, bar: 2}
foo  #=> 1
bar  #=> 2

Here's how I implement the sigil

defmodule DestructingAssignment do
  defmacro __using__(_) do
    quote do: import unquote(__MODULE__)
  end

  defmacro sigil_m({:<<>>, _line, [string]}, []) do
    spec = string
           |> String.split
           |> Stream.map(&String.to_atom/1)
           |> Enum.map(&{&1, {&1, [], nil}})
    {:%{}, [], spec}
  end
end

Usage

defmodule Foo do
  use DestructingAssignment

  def foo(~m{bar}) do
    # Do whatever you want with bar
    IO.inspect(bar)
  end
end

Foo.foo(%{bar: 1, baz: 2})
1
like image 30
Aetherus Avatar answered Oct 11 '22 17:10

Aetherus