3

Checking over the documentation for one of the new R17 features, maps, brought me to maps:remove/2 and maps:without/2. The only clear distinction I can see is that remove/2 takes a single key and returns a view of the map without it, where without/2 accepts a list and returns a completely new map lacking the listed keys.

22> M1 = #{foo => bar, spam => eggs}.
#{foo => bar,spam => eggs}
23> M2 = maps:without([foo], M1).
#{spam => eggs}
24> M3 = maps:remove(foo, M1).
#{spam => eggs}
25> M1.
#{foo => bar,spam => eggs}
26> M2.
#{spam => eggs}
27> M3.
#{spam => eggs}

What is the practical impact of this? I can appreciate not wanting to create in-memory copies of gigantic maps with without/2, but why doesn't remove/2 accept a list? I'm assuming there is a performance-oriented reason why these two functions exist the way they do, but I'm confused about when I would want to use one over the other in most situations (meaning, I don't think that maintaining gigantic maps are a generally good idea).

zxq9
  • 13,020
  • 1
  • 43
  • 60

1 Answers1

10

The first thing about maps is that, the implementation may change. As Fred Hébert wrote in learn you some Erlang maps Chapter: "The OTP team is respecting the old slogan: first make it work, then make it beautiful, and only if you need to, make it fast." So don't depend on this answer too heavily.

Currently the maps:without/2 function is implemented like this:

without(Ks, M) when is_list(Ks), is_map(M) ->
    maps:from_list([{K,V}||{K,V} <- maps:to_list(M), not lists:member(K, Ks)]).

As you can see, it iterates over entire map. It converts it to list, removes the keys and converts it back to map. Not really efficient, but as I said: this may change in the future.

The maps:remove/2 function is a NIF, which means, it is written in C and takes advantage from internal representation. Speaking of which... During Stockholm Erlang Factory 2013 Kenneth Lundin mentioned maps internal representation (http://vimeo.com/69950294). Actually, there are two of them (sliedes are from the talk, that I linked to).

maps internal representation 1

This one is for low amount of keys. In this representation, set of values has pointer to set of keys, which means, that if you change value, but not the key - keys will be shared. Also keys are sorted. Second one, for larger amounts of keys looks like this:

enter image description here

So it is a tree, which means, that if you for example delete a key on the right subtree, your new map could share entire left subtree. For more clear information on immutable data structures, you can refer to wikipedia ErlangVM should transparently switch between those representations as needed.

To answer your question. If you want to delete one key - use maps:remove/2, if you want to delete multiple keys, use maps:without/2, because it might be cheaper to create new map instead of manipulating the old one.

tkowal
  • 9,129
  • 1
  • 27
  • 51
  • Excellent explanation. This gives me a *lot* more insight into how these work (and not just remove/2 and without/2). I'll have to watch that video now. – zxq9 Sep 11 '14 at 09:54
  • We haven't got to the second representation yet. – rvirding Sep 11 '14 at 12:12