PJUllrich
Idiomatic way of handling missing Map fields
I’m extracting many fields from a map using pattern-matching against the function parameter. Sometimes only a single field is missing from the input map. I’m handling this case with Map.merge-ing a default value into the input map and calling the same function again.
However, this feels not quite idiomatic. Can you maybe think of a more idiomatic way of handling this case?
Example:
defmodule Foobar do
def foo(%{"a" => a, "b" => b, "c" => c, "d" => d, "e" => e}), do: do_something(a, b, c, d, e)
def foo(params), do: foo(Map.merge(params, %{"e" => 10}))
def do_something(a, b, c, d, e), do: IO.inspect("#{a}-#{b}-#{c}-#{d}-#{e}")
end
Foobar.foo(%{"a" => 1, "b" => 2, "c" => 3, "d" => 4, "e" => 5})
Foobar.foo(%{"a" => 1, "b" => 2, "c" => 3, "d" => 4})
```
Most Liked
brettbeatty
I don’t think it’s bad to use Map.merge/2, but the way you use it as a fallback seems weird (and you wouldn’t have a base case if your map didn’t have an "a" key, for example).
Here’s how I would write your module:
defmodule Foobar do
@default_params %{"e" => 10}
def foo(params) do
params = Map.merge(@default_params, params)
do_something(params["a"], params["b"], params["c"], params["d"], params["e"])
end
def do_something(a, b, c, d, e) do
IO.inspect("#{a}-#{b}-#{c}-#{d}-#{e}")
end
end
But that may just be because of how I handle options to functions. I write a lot of single-public-function modules where I’m passing around the options to a lot of private functions and want the defaulting to happen in one place. They’ll look something like this:
defmodule My.Service do
@default_opts %{charlie: true, delta: []}
def call(alpha, bravo, opts \\ []) do
opts = Enum.into(opts, @default_opts)
with :ok <- validate_something(alpha, opts),
{:ok, echo} <- do_something(alpha, bravo, opts) do
{:ok, bravo + echo}
end
end
...
end
PJUllrich
Marcus
@sigu
This case should not be the cause of using a struct. I mean the decision for a struct should be related to the problem and not to using some special syntax.
@PJUllrich
The idiomatic way for me is not to use pattern matching just for the case of extracting values from a map. The pattern matching in the parameters should have always a good reason.
For me foocould be written as:
def foo(data) do
do_something(
Map.get(data, "a"),
Map.get(data, "b"),
Map.get(data, "c"),
Map.get(data, "d"),
Map.get(data, "e", 10)
)
end
or
def foo(data) do
defaults = %{"e" => 10}
%{"a" => a, "b" => b, "c" => c, "d" => d, "e" => e} = Map.merge(defaults, data)
do_something(a, b, c, d, e)
end
dimitarvp
@Marcus gives you a good way to go about it. I’ll offer one more:
defmodule Foobar do
def foo(%{"a" => a, "b" => b, "c" => c, "d" => d, "e" => e}), do: do_something(a, b, c, d, e)
+ def foo(%{"a" => a, "b" => b, "c" => c, "d" => d}), do: do_something(Map.merge(params, %{"e" => 10}))
- def foo(params), do: foo(Map.merge(params, %{"e" => 10}))
def do_something(a, b, c, d, e), do: IO.inspect("#{a}-#{b}-#{c}-#{d}-#{e}")
end
PJUllrich
Yes, the missing field is always the same.
Thanks a lot for your solution, however it feels a bit overkill 
But it would be a good solution if any of the fields could be missing.








