Pattern matching to Presence event "presence_diff"

I am puzzled by the callback to the “presence_diff” event from Presence.

I set up Presence.track on mount:

 Presence.track(self(), "presence", System.os_time(:second), %{user_id: user_id})

When a user connects, I inspect the message

def handle_info(data, socket) do
  IO.inspect(data)
  {:noreply, socket}
end

This returns:

data = %Phoenix.Socket.Broadcast{
  topic: "presence",
  event: "presence_diff",
  payload: %{
    joins: %{
      "1669885850" => %{metas: [%{phx_ref: "FyygVNocRKcFfiRl", user_id: 1}]}
    },
    leaves: %{}
  }
}

However, the pattern match below doesn’t work:

def handle_info(%{event: "presence_diff", payload: %{joins: %{}}}, socket ) do
  IO.puts "leaves"
  {:noreply, socket}
end

def handle_info(%{event: "presence_diff", payload: %{leaves: %{}}}, socket ) do
 IO.puts "joins"
 {:noreply, socket}
end

This always returns “leaves” (in this order), so will:

def handle_info(%{event: "presence_diff", payload: %{joins: joins, leaves: leaves}}, socket ) do
case {joins, leaves} do
  { %{}, _} -> 
       IO.puts "leaves"
  { _, %(} } ->
      IO.puts "joins"
end

Only this one works.

def handle_info(%{event: "presence_diff", payload: %{joins: joins, leaves: leaves}}, socket ) do
  cond do
     joins == %(} ->
        IO.puts "leaves"
     leaves == %(} ->
       IO.puts "joins"
  end

Why can’t I use pattern matching?

You can’t patttern match on an empty map - there’s some good information here that explains much better than I could as to why: Pattern Match On Empty Maps

Edit: Just for some clarity - Map — Elixir v1.12.3

Specifically:

Maps can be pattern matched on. When a map is on the left-hand side of a pattern match, it will match if the map on the right-hand side contains the keys on the left-hand side and their values match the ones on the left-hand side. This means that an empty map matches every map.

If I understand correctly, the pattern match order is “left-[message]” to “right-[function argument]”, and not the other way round. Ok, thanks, not obvious!

1 Like

As others have stated, you can’t pattern match on an empty map, but you can use a guard clause in your function header to achieve the same thing. Example:

def handle_info(%{event: "presence_diff", payload: %{joins: joins}}, socket ) when joins == %{} do
  ...
end
def handle_info(%{event: "presence_diff", payload: %{leaves: leaves}}, socket ) when leaves == %{} do
  ...
end
1 Like

Yes, indeed the guard. I just didn’t see that both %{leaves: %{}} = %{joins: 1, leaves: %{}} and %{leaves: %{}} = %{joins: %{}, leaves: %{a: 1}} match, thus useless, hence a direct condition on the map is needed. Beginners trap. => Docs should show such examples.