Issue #19272 has been updated by nobu (Nobuyoshi Nakada).
zverok (Victor Shepelev) wrote:
E.g.: **If, and only if, the passed block is of arity
2, treat it as an operation on old and new values.** Otherwise, proceed as before
(maintaining backward compatibility.)
Usage:
```ruby
{apples: 1, oranges: 2}.merge(apples: 3, bananas: 5, &:+)
#=> {:apples=>4, :oranges=>2, :bananas=>5}
```
`:+.to_proc` is a proc just calls `+` method on the first argument with the rest.
That means its arity is not deterministic.
```ruby
{words: %w[I just]}.merge(words: %w[want a group], &:concat)
#=> {:words=>["I", "just", "want", "a",
"group"]}
```
In this example, you expect `Array#concat` on the old values, but the arity of
`Array#concat` is -1 not 2.
----------------------------------------
Feature #19272: Hash#merge: smarter protocol depending on passed block arity
https://bugs.ruby-lang.org/issues/19272#change-101664
* Author: zverok (Victor Shepelev)
* Status: Open
* Priority: Normal
----------------------------------------
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) { |_, o, n| block.call(o, n) }
else
old_merge(other, &block)
end
end
end
```
E.g.: **If, and only if, the passed block is of arity 2, treat it as an operation on old
and new values.** Otherwise, proceed as before (maintaining backward compatibility.)
Usage:
```ruby
{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, the 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 the 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.
--
https://bugs.ruby-lang.org/