There's any difference between map.put/3 and the built in Map update %{map | "key" => "value"}?

Hello, guys!

I was doing some exercises from exercism.io and in some moment a needed to update a Map, I did and finished the exercise. After a while, I found the function Enum.frequencies/1 that could have helped me on the exercise. I decided open Elixir source code to see implementation of Enum.frequencies/1 that is this:

  @doc since: "1.10.0"
  @spec frequencies(t) :: map
  def frequencies(enumerable) do
    reduce(enumerable, %{}, fn key, acc ->
      case acc do
        %{^key => value} -> %{acc | key => value + 1}
        %{} -> Map.put(acc, key, 1)
      end
    end)
  end

Then, the question came to me: There’s any difference between map.put/3 and the built in Map update %{map | “key” => “value”}?

I write my version of Enum.frequencies/1 using Enum.reduce/3, and I replaced %{^key => value} -> %{acc | key => value + 1} by %{^x => value} -> Map.put(acc, x, value + 1) and did work.
I would like to know if there is any reason for Elixir source code to use this syntax %{acc | key => value + 1} on Enum.frequencies/1

1 Like

Hey, nice to see you are checking source code for solving doubts. Yes there’s a difference, when you update a map using the | operator, that key has to exist already. That is the reason on the case, if the key already exists they just increase the counter by updating the map using the | operator, but if the key is not present yet, the added it using the Map.put/3 function.

6 Likes

Thank you for explaining the differences, @tovarchristian21 .

But if the key already exists, Map.put/3 can also update the value of this existent key, right? Is there some situation that would be not good to have Map.put/3 instead %{map | “key” => “value”}`?

This doubt is just out of curiosity. :upside_down_face:

Yes, Map.put/3 can also update the value of an existent key. I would say that the right comparison here is with Map.update!/3 because using the shorthand update will lead to an update like this function does. So the thing that matters is if the key is present or not, you might want to know when trying to update a map if a certain key is present, because if it does not it can lead into an exception raise which you might be expecting, and using Map.put/3 will simply ignore that condition and set the key to the wanted value.

1 Like

This is the equivalent of doing Map.update(acc, key, 1, &(&1 + 1))

3 Likes

In erlang, Map#{key => Value} means update or insert, and Map#{key := Value} means update only. In elixir the shorthand for upsert is removed to reduce confusion. So this is also a good example to showcase the difference two languages’ philosophy: Erlang favors logic and completeness, Elixir favors ergonomics and sane default.

2 Likes

FWIW, apparently (in 2019) the overhead of calling an anonymous function &(&1 + 1) was enough to inline Map.update in Enum.frequencies:

2 Likes

Oh, this interesting, @al2o3cr ! That was what I was looking for. Thank you.

Thanks to everyone for the answares.