Feature #8772

Hash alias #| merge, and the case for Hash and Array polymorphism

Ideally Hash and Array would be completely polymorphic in every manner in which it is possible for them to be so. The reason for this is very simple. It makes a programmer's life easier. For example, in a recent program I was working on, I had a list of keyboard layouts.

layouts = [layout1, layout2, layout3]

Later I realized I wanted to identify them by a label not an index. So...

layouts = {:foo => layout1, :bar => layout2, :baz => layout3}

Unfortunately this broke my program in a number of places, and I had to go through every use of layouts to translate what was an Array call into a Hash call. If Array and and Hash were more polymorphic I would have only had to adjust the places were I wanted to take advantage of the Hash. Ideally almost nothing should have actually broken.

The achieve optimal polymorphism between Hash and Array is to treat a Hash's keys as indexes and its values as as the values of an array. e.g.

Of course the ship has already sailed for some methods that are not polymorphic, in particular #each. Nonetheless it would still be wise to try to maximize the polymorphism going forward. (Perhaps even to be willing to take a bold leap in Ruby 3.0 to break some backward compatibility to improve upon this.)

In the mean time, let us consider what it might mean for Hash#+ as an alias for #merge, if the above were so:

Bingo. So I formally stand corrected. The best alias for merge is #| not #+.

Based on this line of reasoning I formally request the Hash#| be an alias of Hash#merge.

P.S. Albeit, given the current state of polymorphism between Ruby's Array and Hash, and the fact that it will probably never be improved upon, I doubt it really matters which operator is actually used.

This wouldn't head towards any polymorphic approach, but isn't << a better operator for merging? It fells like you're shoving all the contents of the argument to the receiver hash. I think it represents better the functionality of the method.

This wouldn't head towards any polymorphic approach, but isn't << a better operator for merging? It fells like you're shoving all the contents of the argument to the receiver hash. I think it represents better the functionality of the method.¶

Ideally Hash and Array would be completely polymorphic in every manner in which it is possible for them to be so. The reason for this is very simple. It makes a programmer's life easier. For example, in a recent program I was working on, I had a list of keyboard layouts.

layouts = [layout1, layout2, layout3]

Later I realized I wanted to identify them by a label not an index. So...

layouts = {:foo => layout1, :bar => layout2, :baz => layout3}

Unfortunately this broke my program in a number of places, and I had to go through every use of layouts to translate what was an Array call into a Hash call. If Array and and Hash were more polymorphic I would have only had to adjust the places were I wanted to take advantage of the Hash. Ideally almost nothing should have actually broken.

The achieve optimal polymorphism between Hash and Array is to treat a Hash's keys as indexes and its values as as the values of an array. e.g.

Of course the ship has already sailed for some methods that are not polymorphic, in particular #each. Nonetheless it would still be wise to try to maximize the polymorphism going forward. (Perhaps even to be willing to take a bold leap in Ruby 3.0 to break some backward compatibility to improve upon this.)

In the mean time, let us consider what it might mean for Hash#+ as an alias for #merge, if the above were so:

Bingo. So I formally stand corrected. The best alias for merge is #| not #+.

Based on this line of reasoning I formally request the Hash#| be an alias of Hash#merge.

P.S. Albeit, given the current state of polymorphism between Ruby's Array and Hash, and the fact that it will probably never be improved upon, I doubt it really matters which operator is actually used.

The #<< operator does quite a different thing than either #merge or #reverse_merge:

[1,2] << [3,4] #=> [1, 2, [3, 4]]

Arrays are quite a different thing from Hashes, so it would not be surprising for Array#<< to do quite a different thing than Hash#<<, but the general concept in both cases is the same: "insert the operand into the receiver".

In the case of Array, the operand can be inserted as-is.

In the case of Hash, the operand cannot be inserted as-is because it's not clear whether to treat the operand as a key with a nil value (not very useful for Hash) or as a value with an unspecified key (also not useful for Hash). Instead, it does make sense to treat the operand to Hash#<< as a collection of key/value pairs to be inserted, which makes it very much like Hash#merge!.

So, yea +1 for #<<. But it is not the exact same thing as #merge, or even #merge!.

Do you mean that it is not the exact same thing as #merge! because it would accept objects other than Hashes? Do you agree that given a Hash argument it would be equivalent to Hash#merge!? If yes to both of those then our thoughts have merged! :-)

The #<< operator does quite a different thing than either #merge or #reverse_merge:

[1,2] << [3,4] #=> [1, 2, [3, 4]]

Arrays are quite a different thing from Hashes, so it would not be surprising for Array#<< to do quite a different thing than Hash#<<, but the general concept in both cases is the same: "insert the operand into the receiver".

I agree that Array#<<, Fixnum#<<, and String#<< are not awfully similar. Maybe it is ok to use Hash#<< as #merge! or similar. It would be nice however IMO to have something like that for #reverse_merge! too.

Maybe it is ok to use Hash#<< as #merge or similar. It would be nice however IMO to have something similar for #reverse_merge too.

I completely agree that it would be nice to have an operator for Hash that behaved similar to reverse_merge. Using Hash#| as a reverse_merge-ish operator is being discussed in #7739. I think I would end up using the reverse_merge-ish operator more than the merge-ish operator.

Ideally Hash and Array would be completely polymorphic in every manner in which it is possible for them to be so. The reason for this is very simple. It makes a programmer's life easier. For example, in a recent program I was working on, I had a list of keyboard layouts.

layouts = [layout1, layout2, layout3]

Later I realized I wanted to identify them by a label not an index. So...

layouts = {:foo => layout1, :bar => layout2, :baz => layout3}

Unfortunately this broke my program in a number of places, and I had to go through every use of layouts to translate what was an Array call into a Hash call. If Array and and Hash were more polymorphic I would have only had to adjust the places were I wanted to take advantage of the Hash. Ideally almost nothing should have actually broken.

The achieve optimal polymorphism between Hash and Array is to treat a Hash's keys as indexes and its values as as the values of an array. e.g.

Of course the ship has already sailed for some methods that are not polymorphic, in particular #each. Nonetheless it would still be wise to try to maximize the polymorphism going forward. (Perhaps even to be willing to take a bold leap in Ruby 3.0 to break some backward compatibility to improve upon this.)

In the mean time, let us consider what it might mean for Hash#+ as an alias for #merge, if the above were so:

Bingo. So I formally stand corrected. The best alias for merge is #| not #+.

Based on this line of reasoning I formally request the Hash#| be an alias of Hash#merge.

P.S. Albeit, given the current state of polymorphism between Ruby's Array and Hash, and the fact that it will probably never be improved upon, I doubt it really matters which operator is actually used.

Ideally Hash and Array would be completely polymorphic in every manner in
which it is possible for them to be so. The reason for this is very simple.
It makes a programmer's life easier. For example, in a recent program I was
working on, I had a list of keyboard layouts.

layouts = [layout1, layout2, layout3]

Later I realized I wanted to identify them by a label not an index. So...

layouts = {:foo => layout1, :bar => layout2, :baz => layout3}

Unfortunately this broke my program in a number of places, and I had to go
through every use of layouts to translate what was an Array call into a
Hash call. If Array and and Hash were more polymorphic I would have only
had to adjust the places were I wanted to take advantage of the Hash.
Ideally almost nothing should have actually broken.

The achieve optimal polymorphism between Hash and Array is to treat a
Hash's keys as indexes and its values as as the values of an array. e.g.

Of course the ship has already sailed for some methods that are not
polymorphic, in particular #each. Nonetheless it would still be wise to try
to maximize the polymorphism going forward. (Perhaps even to be willing to
take a bold leap in Ruby 3.0 to break some backward compatibility to
improve upon this.)

In the mean time, let us consider what it might mean for Hash#+ as an
alias for #merge, if the above were so:

Bingo. So I formally stand corrected. The best alias for merge is #| not
#+.

Based on this line of reasoning I formally request the Hash#| be an alias
of Hash#merge.

P.S. Albeit, given the current state of polymorphism between Ruby's Array
and Hash, and the fact that it will probably never be improved upon, I
doubt it really matters which operator is actually used.

Ideally Hash and Array would be completely polymorphic in every manner in
which it is possible for them to be so. The reason for this is very simple.
It makes a programmer's life easier. For example, in a recent program I was
working on, I had a list of keyboard layouts.

layouts = [layout1, layout2, layout3]

Later I realized I wanted to identify them by a label not an index. So...

layouts = {:foo => layout1, :bar => layout2, :baz => layout3}

Unfortunately this broke my program in a number of places, and I had to go
through every use of layouts to translate what was an Array call into a
Hash call. If Array and and Hash were more polymorphic I would have only
had to adjust the places were I wanted to take advantage of the Hash.
Ideally almost nothing should have actually broken.

The achieve optimal polymorphism between Hash and Array is to treat a
Hash's keys as indexes and its values as as the values of an array. e.g.

Of course the ship has already sailed for some methods that are not
polymorphic, in particular #each. Nonetheless it would still be wise to try
to maximize the polymorphism going forward. (Perhaps even to be willing to
take a bold leap in Ruby 3.0 to break some backward compatibility to
improve upon this.)

In the mean time, let us consider what it might mean for Hash#+ as an
alias for #merge, if the above were so:

Bingo. So I formally stand corrected. The best alias for merge is #| not
#+.

Based on this line of reasoning I formally request the Hash#| be an alias
of Hash#merge.

P.S. Albeit, given the current state of polymorphism between Ruby's Array
and Hash, and the fact that it will probably never be improved upon, I
doubt it really matters which operator is actually used.

The reason for defining (({#|})) as reverse merge is to have the closest behavior to "(({1 || 2}))", as i said in #7739.
The operator (({#&})) for hashes does not seem very useful on its own, so it is invented only for illustration.
=end

=begin
Allowing Hash#<< to take a key-value pair, i.e. [:a,1], provides useful polymorphism between hash and associative arrays. And that's because in Ruby the way it represents a key-value pair is via a two-element array. We see this when use Hash.each { |q| .. }, q is an array pair. So for example:

We were able to use the same routine regardless of whether i is an array or a hash. And notice that Hash#to_a produces the associative array.

_.to_a #=> [[:a,1], [:b,2]]

Without this polymorphism, we would have to write more complex code to handle an array vs a hash case. But by simpling allowing #<< to handle key-value pairs, we get a lot more bang for our coding buck.
=end