I have come across wanting to do this a couple of times in the past as well.

But before that, I do want to mention that there are two related situations 
that have more elegant solutions:

1) You want to create a map or structure where fields that were not 
supplied (e.g. by the user request) fall back to a default. This can be 
done with patterns like this:
query = %MyRequest{
            language: params["language"] || "english", 
            id: params["id"] || session.id
        }

2) You want to pick one of a restricted set of choices; also known as 
working with a 'sum type':
shape = case params["shape"] do
          "square" -> 
            %Square{position: params["position"], width: params["width"], 
height: params["height"]}
          "circle" -> 
             %Circle{position: params["position"], radius: params["radius"]}
           _ ->
            raise ArgumentError, "unknown kind of shape provided"
        end

If you are able to use either of these approaches in a specific use-case, 
then you can reduce the number of input checks you need to do later on. (In 
your original example code, all functions operating on `query` will have to 
check again if `:language` and/or `:id` exist.)


Having said that, there definitely are cases where you want to normalize a 
map with string keys to a map with recognized atom keys (to ensure that (a) 
you dont exhaust memory by creating more and more atoms and (b) reject 
inputs that you cannot handle).
The pattern I have used in the past was similar to:

def normalize_keys(into \\ %{}, input, accepted_keys) do
  string_keys = MapSet.new(accepted_keys, &to_string/1)

  Enum.reduce(input, into, fn {string_key, val}, acc ->
     if string_key in string_keys do
       put_in(acc, [:"#{string_key}"], val)
     else
       acc
     end
  end)
end
And use it as:

iex> normalize_keys(%{"language" => "english", "id" => 33, "disregard_me" => 
10}, [:id, :language])
%{id: 33, language: "english"}

Do note that this has a slightly different result from your original code, 
since `string_key in string_keys` checks for the existance of a key, while 
`params[string_key]` checks for the existance of a key AND the value stored 
under key being truthy.

Only after writing out this code I realize its similarity to 
Ecto.Changeset.cast/4 <https://hexdocs.pm/ecto/Ecto.Changeset.html#cast/4> 
which seems to deal with a variant of the same problem. 
A common pattern for sure, interesting.

~Marten / Qqwy

-- 
You received this message because you are subscribed to the Google Groups 
"elixir-lang-core" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to elixir-lang-core+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/elixir-lang-core/d307d598-a012-4b41-9700-c31f8fc07faa%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to