Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pattern matching map with optional keys

Tags:

elixir

I have a function which receives a map with many keys, some of them are optional. How can I write the function signature comprehending the map while allowing the optional keys to default to something?

def handle_my_map(%{text: text, 
                    print_times: print_times, # this I want to default to 2
                    color: color # this I want to default to "Blue"
  }) do
  Enum.each(1..print_times, fn (_) -> IO.puts ["(", color, "): ", text] end)
end

Test.handle_my_map(%{text: "here", print_times: 5, color: "Red"})
# (Red): here
# (Red): here
# (Red): here
# (Red): here
# (Red): here

handle_my_map(%{text: "there"})
# => MatchError!

I would like it to be:

handle_my_map(%{text: "where", print_times: 3})
# (Blue): where
# (Blue): where
# (Blue): where
handle_my_map(%{text: "there"})
# (Blue): there
# (Blue): there

Something like ruby's keyword arguments:

def handle_my_map(text: nil, print_times: 2, color: 'Blue')
like image 327
Uri Agassi Avatar asked Jul 16 '14 08:07

Uri Agassi


3 Answers

You can use Map.merge/2:

defmodule Handler do
  @defaults %{print_times: 2, color: "Blue"}

  def handle_my_map(map) do
    %{text: text, print_times: times, color: color} = merge_defaults(map)
    Enum.each(1..times, fn (_) -> IO.puts ["(", color, "): ", text] end)
  end

  defp merge_defaults(map) do
    Map.merge(@defaults, map)
  end
end

If you want to allow nils, you can use Map.merge/3 and change merge_defaults/1 to:

defp merge_defaults(map) do
  Map.merge(@defaults, map, fn _key, default, val -> val || default end)
end
like image 169
José Valim Avatar answered Nov 18 '22 17:11

José Valim


I would probably go with something like this:

defmodule Handler do
  @defaults %{print_times: 2, color: "Blue"}

  def handle_my_map(map) do
    %{text: text, print_times: times, color: color} = Dict.put_new(map, @defaults)
    Enum.each(1..times, fn (_) -> IO.puts ["(", color, "): ", text] end)
  end
end

If you need to handle nil values with existing keys, you can do:

defmodule Handler do
  @defaults %{print_times: 2, color: "Blue"}

  def handle_my_map(map) do
    %{text: text, print_times: times, color: color} = @defaults
    |> Dict.merge(map)
    |> Enum.into %{}, fn
      {key, nil} -> {key, @defaults[key]}
      {key, val} -> {key, val}
    end

    Enum.each(1..times, fn (_) -> IO.puts ["(", color, "): ", text] end)
  end
end
like image 3
Chris McCord Avatar answered Nov 18 '22 17:11

Chris McCord


Well. I think you need to write another one handle_my_map function with argument %{a: a, b: b}. Like this:

  def handle_my_map(%{a: a, b: b, optional_c: c}) do
    a + b + c
  end

  def handle_my_map(%{a: a, b: b}) do
    a + b
  end

  YourModule.handle_my_map %{a: 1, b: 2}
  #=> 3
  YourModule.handle_my_map %{a: 1, b: 2, optional_c: 3}
  #=> 6

Elixir will search for a function handle_my_map which matches your arguments until functions with arity 1 end

like image 1
vforvova Avatar answered Nov 18 '22 17:11

vforvova