rwehresmann
rwehresmann

Reputation: 1168

Pattern matching with a map inside a map (Elixir)

I have the following function working in an Elixir app:

  def emojify(%Conv{status: 200, resp_headers: resp_headers} = conv) do
    body = if resp_headers["Content-Type"] == "application/json", do: conv.resp_body, else: "🎉" <> "\n\n" <> conv.resp_body <> "\n\n" <> "🎉"
  
    %{ conv | resp_body: body }
  end

  def emojify(%Conv{} = conv), do: conv

I'd like to refactor it to eliminate the need for the if inside it. I imagine that the way to do it is through pattern matching the content-type inside the resp_headers, but that would be pattern matching of a map inside a map (a struct inside a map to be more precise, but I don't think this detail is relevant in this case), and I'm not sure how to do that.

I was trying something like def emojify(%Conv{status: 200, resp_headers: %{"Content-Type" => "application/json"}} = conv), but I understand that this would only work if resp_headers has only "Content-Type" => "application/json" inside it, and that isn't the case. So how could I accomplish this?

Upvotes: 0

Views: 1044

Answers (2)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

I understand that this would only work if resp_headers has only "Content-Type" => "application/json" inside it

This assumption is wrong. The map matches if the fields in the match are presented in the input and all pinned variables are matched. You have no pinned variables, so you are OK.

Match to the empty map is frequently used instead of is_map(map) guard as foo(%{} = map) do ...

%{} = %{foo: 42, bar: :baz} # success

# and also
%{foo: foo} = %{foo: 42, bar: :baz} # success
foo
#⇒ 42

That said, the answer would be

def emojify(%Conv{
    status: 200,
    resp_headers: %{"Content-Type" => "application/json"}} = conv),
  do: %{ conv | resp_body: conv.resp_body }

def emojify(%Conv{status: 200} = conv),
  do: %{ conv | resp_body: "🎉" <> "\n\n" <> conv.resp_body <> "\n\n" <> "🎉"}

def emojify(%Conv{} = conv), do: conv

Sidenote: example with pinned variables.

%{foo: 42} = %{foo: 42, bar: :baz} # success

foo = 42
%{foo: ^foo} = %{foo: 42, bar: :baz} # success

%{foo: ^foo} = %{foo: :incorrect, bar: :baz} # raises

Upvotes: 3

okoth kongo
okoth kongo

Reputation: 71

You can pattern-match like so

def emojify( %{status: 200, resp_headers: %{"Content-Type" => "application/json" } } = conv
) do
    %{ conv | resp_body: conv.resp_body }    
   end
   def emojify(%{status: 200} = conv) do
    %{ conv | resp_body: "🎉" <> "\n\n" <> conv.resp_body <> "\n\n" <> "🎉" }
   end
   def emojify(conv), do: conv

You can always pattern-match values of specific keys in a map or a struct

For more check this out joyofelixir.

Upvotes: 1

Related Questions