Does the `Kernel.pop_in/2` function actually do the right when facing entries being nil?

The documentation of Kernel.pop_in/2 states the following:

In case any entry returns nil , its key will be removed and the deletion will be considered a success.

The accompanying example shows a nested map where the first entry (from the path passed to pop_in) is not found in the passed data:

iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}}
iex> pop_in(users, ["jane", :age])
{nil, %{"john" => %{age: 27}, "meg" => %{age: 23}}}

Nothing notable happens to the map as there is no "jane" key at all, as expected.

But how about "jane" being there but not having an age field or even not having any data assigned at all (yet)?

iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}, "jane" => %{}}
iex> pop_in(users, ["jane", :age])                                      
{nil, %{"jane" => %{}, "john" => %{age: 27}, "meg" => %{age: 23}}}

Here an empty map was assigned to jane and we got an empty map for jane back. Still seems to be allright. But when the value for "jane" is set to nil, the pop_in/2 function will remove the "jane" key.

iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}, "jane" => nil}
iex> pop_in(users, ["jane", :age])
{nil, %{"john" => %{age: 27}, "meg" => %{age: 23}}}

This matches the description inside the documentation to the letter… Maybe it’s just me, but does it make sense to remove "jane" altogether while pop_in/2 targets the :age field within the data for "jane"?
In other words: Does it make sense to remove the player "Elmo" in the following example entirely when targeting the :cash field inside his :inventory but he actually hasn’t any associated data yet?

iex> players = %{"Elmo" => nil}
iex> pop_in(players, ["Elmo", :inventory, :cash])                   
{nil, %{}}
2 Likes

I think the way to think about this is that Elmo does it fact have data - its just that the data is the atom nil.

1 Like

Do you mean that it does not feel right to you either because Elmo’s data (even though it is “just” nil) is removed entirely? (which is indeed what the op is about)

It definitely feels wrong to me, though I also feel like you should never wind up in that situation. at the very least this behaviour should be documented.

1 Like

I think the documentation is very clear about this specific case. But it does seem unexpected.

1 Like

Wow. I missed that sentence! Maybe it should be put in a warning block!

I also mentioned in the OP that pop_in/2 behaves exactly as it is documented. I also missed that initially so that could be emphasized :slightly_smiling_face:

Anyway, my point is to question whether it is sensible to remove the data altogether. I am glad that there are others finding the current behavior counter-intuitive to a varying degree!

It seems that the pop_in/2 function came to be as a renamed modification of a delete_in/2 function. Maybe it does make a little more sense in the context of deleting things nested inside a data structure, I don’t know.
Anyway, I hope he doesn’t mind dragging him into this, but @josevalim, what is your point of view in this matter?