
Map.merge() – One method to rule them all - wheresvic1
https://www.nurkiewicz.com/2019/03/mapmerge-one-method-to-rule-them-all.html
======
browda
Yes, the final example code reads reasonably nicely, but alas -- unless
ConcurrentHashMap.merge() has been rewritten since Java 8 time -- it is
definitely not as performant as the author appears to think it is since the
actions will be contained in a fairly long synchronized block. This is not to
say the code is bad, but you can't get around that.

Somewhat surprisingly, the code the author pooh-poohs is actually more
performant (although it's better if the test is for the presence of the key
rather than the "putIfPresent/computeIfAbsent combination) because the JVM
will properly optimize a membership test (in keyset) and the use of a ternary
expression makes the action clear.

------
_old_dude_
yes, Java 8 adds a bunch of methods to ArrayList and HashMap that makes the
code a little more high level.

There is also another variation, using map.getOrDefault()

    
    
      words.forEach(word -> {
          map.put(word, map.getOrDefault(word, 0) + 1);
      });
    

and then at some points you discover the Stream API

    
    
      var map = words.stream().collect(Collectors.groupingBy(w -> w, Collectors.counting()));

~~~
wcarss
in python:

    
    
        >>> from collections import defaultdict
        >>> map = defaultdict(int)
        >>> for word in ['hi', 'there', 'hi']:
        ...   map[word] += 1
    

I am glad Java has this stuff now, but it's a little surprising that its tools
are still so obtuse relative to competitors.

edit: I should have foreseen the derailment caused by and nitpicking to follow
a comment like this. Sorry folks!

~~~
meddlepal
Yes... now turn that map into a lock free thread-safe dict in Python and show
us the same code. I get that for free in Java via ConcurrentHashMap.

~~~
ben-schaaf
If you mean lock-free as in no GIL, you obviously can't. But otherwise all
python code is inherently thread safe.

~~~
meddlepal
Yea I meant lock free and ready for multi-threaded use without a GIL. I didn't
mean to dump on Python either, I write a lot of Python and Java but
comparisons like yours aren't really fair because the JVM is just considerably
more powerful than Python's runtime. I could take a huge list of words, hand
subsets to threads and let them all update the same map using the same API and
without needing a lock.

------
kazinator
This is a poor semantics. The function should always be called. In the non-
existent-key case, it should be invoked on the default value that is supplied.
Thus the example of the histogram tabulation would use 0 as the initial value.

The name is ill-chosen; this mutator doesn't "merge" at all; it "updates" the
map entry through a function, with a default value.

"merge" might be a somewhat good name for an operation that combines two maps,
with a function that resolves key clashes; though since that is a kind of set
union, that provides better terminology.

------
vojtisek
I like how the more mathematical concepts get their way to the mainstream
languages. It's making concepts such as monoids and semigroups more accessible
to general public.

------
twic
The one thing that frustrates me about merge is that it returns the value
inserted, rather than the value being replaced (as Map::put does).

There are good uses cases for what it does - but there are also good use cases
for returning the previous value, and when i have one of those, i can't use
merge, and have to fall back to a get followed by a put.

Perhaps what i would really like is a Rust-style entry API, where i could use
a key to obtain a Map.Entry, which i could then inspect and mutate how i
liked. It might look like:

    
    
      Entry<K, V> getOrCreateEntry(K key, Function<? super K,  ? extends V> mappingFunction)
    

And then i could write:

    
    
      void sell(Potato) throws FarmException { ...}
    
      Map<Field, Potato> potatoesByField = new HashMap<>();
      for (var potato: getAllPotatoes()) {
        var fieldAndPotato = potatoesByField.getOrCreateEntry(potato.getField(), f -> potato);
        var oldPotato = fieldAndPotato.getValue();
        if (potato.getWeight() > oldPotato.getWeight()) {
          potatoAndField.setValue(potato);
          sell(oldPotato);
        } else {
          sell(potato);
        }
      }
    

To collect my prize potatoes into a map, and sell the rest. Still ugly, but
hey, at least it's not Go.

~~~
barrkel
Your API works less well in concurrent map scenarios, though.

------
johnchristopher
Nice, so basically it's `INSERT ... ON DUPLICATE KEY UPDATE` ?

~~~
lmz
It's also MERGE in the SQL standard:
[https://en.m.wikipedia.org/wiki/Merge_(SQL)](https://en.m.wikipedia.org/wiki/Merge_\(SQL\))

------
balodja
Monads do exist in the programming for a reason.

