Feature #19272
Updated by zverok (Victor Shepelev) almost 2 years ago
Usage of `Hash#merge` with a "conflict resolution block" is almost always clumsy: due to the fact that the block accepts `|key, old_val, new_val|` arguments, and many trivial usages just somehow sum up old and new keys, the thing that should be "intuitively trivial" becomes longer than it should be: ```ruby # I just want a sum! {apples: 1, oranges: 2}.merge(apples: 3, bananas: 5) { |_, o, n| o + n } # I just want a group! {words: %w[I just]}.merge(words: %w[want a group]) { |_, o, n| [*o, *n] } # I just want to unify flags! {'file1' => File::READABLE, 'file2' => File::READABLE | File::WRITABLE} .merge('file1' => File::WRITABLE) { |_, o, n| o | n } # ...or, vice versa: {'file1' => File::READABLE, 'file2' => File::READABLE | File::WRITABLE} .merge('file1' => File::WRITABLE, 'file2' => File::WRITABLE) { |_, o, n| o & n } ``` It is especially noticeable in the last two examples, but the _usual_ problem is there are too many "unnecessary" punctuation, where the essential might be lost. There are proposals like #19148, which struggle to define _another_ method (what would be the name? isn't it just merging?) But I've been thinking, can't the implementation be chosen based on the arity of the passed block?.. Prototype: ```ruby class Hash alias old_merge merge def merge(other, &block) return old_merge(other) unless block if block.arity.abs == != 2 old_merge(other, &block) else old_merge(other) { |_, o, n| block.call(o, n) } else old_merge(other, &block) end end end {apples: 1, oranges: 2}.merge(apples: 3, bananas: 5, &:+) #=> {:apples=>4, :oranges=>2, :bananas=>5} {words: %w[I just]}.merge(words: %w[want a group], &:concat) => {:words=>["I", "just", "want", "a", "group"]} {'file1' => File::READABLE, 'file2' => File::READABLE | File::WRITABLE} .merge('file1' => File::WRITABLE, &:|) # => {"file1"=>5, "file2"=>5} {'file1' => File::READABLE, 'file2' => File::READABLE | File::WRITABLE} .merge('file1' => File::WRITABLE, 'file2' => File::WRITABLE, &:&) # => {"file1"=>0, "file2"=>4} # If necessary, old protocol still works: {apples: 1, oranges: 2}.merge(apples: 3, bananas: 5) { |k, o, n| k == :apples ? 0 : o + n } # => {:apples=>0, :oranges=>2, :bananas=>5} ``` As far as I can remember, Ruby core doesn't have methods like this (that change implementation depending on arity of passed callable), but I think I saw this approach in other languages. Can't remember particular examples, but always found this idea appealing.