Feature #6225

Hash#+

Added by Thomas Sawyer about 2 years ago. Updated 8 months ago.

[ruby-core:43904]
Status:Rejected
Priority:Normal
Assignee:Yukihiro Matsumoto
Category:core
Target version:next minor

Description

Strings and Arrays can be combined with #+. I don't see any reason not to allow Hashes to do so as well.

class Hash
alias :+ :merge
end

History

#1 Updated by Yusuke Endoh about 2 years ago

  • Status changed from Open to Assigned
  • Assignee set to Yukihiro Matsumoto

Both String#+ and Array#+ delete no information, but
Hash#merge deletes duplicate fields.
I have heard it is the reason, if I recall.

Yusuke Endoh mame@tsg.ne.jp

#2 Updated by Rodrigo Rosenfeld Rosas about 2 years ago

This argument really doesn't buy me. Groovy allows: [key1: 'value1', another: 1] + [key2: 'value2', another: 2] == [key1: 'value1', another: 2, key2: 'value2'].

I think this is pretty readable as + is also used to denotate unions. I don't think anyone would expect any behavior different from that.

Maybe someone could argue that the result could also mean [key1: 'value1', another: [1, 2], key2: 'value2'] but I think that would be really strange.

#3 Updated by Shyouhei Urabe about 2 years ago

I object. No binary operations shall be called + unless the operation is symmetric.
For historical reasons there are some asymmetric +s in Ruby, but that is not a indulgence for you to add more.

#4 Updated by Rodrigo Rosenfeld Rosas about 2 years ago

In other words you don't like that {a: 1} + {a: 2} != {a: 2} + {a: 1}

But I really think programming is different from mathematics and I don't think that the fact that a + b != b + a would be enough reason for avoiding the convenient operator por "b merged to a" (a + b).

Not that I really do care that much about this feature request as I don't see any problem on writing a.merge(b) anyway... I just don't see any problems either for having "a + b == a.merge(b)".

#5 Updated by Thomas Sawyer about 2 years ago

"No binary operations shall be called + unless the operation is symmetric."

Why?

Also what do you mean by symmetric? Do you mean commutative? I point out that neither Array#+ or String#+ is really commutative either because order is significant.

Also, I don't know why you say "indulgence". It's a simple convenience as a means of writing concise, yet readable, code.

#6 Updated by Shyouhei Urabe about 2 years ago

Sorry for my bad English, I dodn't intend to attack you.

Anyway there has been a long discussion around +s in programming languages. For instance Perl uses dot to concatenate strings and avoid +s to concatename strings and/or arrays. I see they are much more mature than us in this area. Functional languages like Haskell also avoid defining + onto non-abelian groups.

#7 Updated by Martin Dürst about 2 years ago

In common sense, * is also commutative. But of course, for matrix multiplication, it's not.

Also, + is used in many fields of mathematics. I'm not a mathematician, but I very strongly doubt that it's commutative in all these cases. (see e.g. http://arxiv.org/abs/1003.2081 for an example)

What's much more important is whether the + for Hash fits the general image a Ruby programmer has for +. I'm not exactly sure about this, but the parallel with Array is not too bad.

#8 Updated by Yukihiro Matsumoto about 2 years ago

I myself do not care whether + to be symmetric or not. I care about key conflict.
Since conflicting cause value lost, I am not positive about making + alias to #merge.

Matz.

#9 Updated by Yui NARUSE about 2 years ago

shyouhei (Shyouhei Urabe) wrote:

Anyway there has been a long discussion around +s in programming languages. For instance Perl uses dot to concatenate strings and avoid +s to concatename strings and/or arrays. I see they are much more mature than us in this area. Functional languages like Haskell also avoid defining + onto non-abelian groups.

On Perl, it is because for
perl -e'print "1" + "2"' #=> 3
perl -e'print "1" . "2"' #=> 12

Not because of symmetry.

#10 Updated by Jackson Willis about 2 years ago

Would it be better to use Hash#<<' instead ofHash#+'?

#11 Updated by Thomas Sawyer about 2 years ago

@jacksonwillis #<< makes sense as an alias for #update, not #merge. However I use Hash#<< with this meaning:

def <<(array)
raise if array.size != 2
self[array.first] = array.last
end

There are historical reasons for that definition. But the two can be combined:

def <<(object)
case object
when Array
raise if object.size != 2
self[object.first] = object.last
else
update(object)
end
end

#12 Updated by Alexey Muranov about 2 years ago

Martin, in math, it is common to use * for both commutative and non-commutative operations but + for only commutative.

But i am more in favor of Matz's argument, because i didn't bother myself about the fact that string addition is non-commutative.

duerst (Martin Dürst) wrote:

In common sense, * is also commutative. But of course, for matrix multiplication, it's not.

Also, + is used in many fields of mathematics. I'm not a mathematician, but I very strongly doubt that it's commutative in all these cases. (see e.g. http://arxiv.org/abs/1003.2081 for an example)

What's much more important is whether the + for Hash fits the general image a Ruby programmer has for +. I'm not exactly sure about this, but the parallel with Array is not too bad.

#13 Updated by Alexey Muranov about 2 years ago

=begin
How about (({Hash#|})) for (({Hash#reversemerge})), and (({Hash#|=})) for (({Hash#reversemerge!})) from ((Rails))? (Instead of (({#+})) for (({#merge})).)

I would like to give an algebraic counterpart to Matz's objection: (({#merge})) is not ((injective)) in the first argument, nor in the second: (({a.merge(b) == a.merge(c)})) does not imply(({ b == c})), but most uses of (({#+})) are injective in each of the arguments.

I know that (({Set#+})) is already an exception to this rule. It seems that it is equivalent to (({Set#|})), isn't it? However, this could be more of a poor definition of (({Set#+})) than a justification to allow (({Hash#+})) to be a synonym for (({#merge})). I was just looking through "Lectures on ergodic theory" by P. Halmos, and there the (({+})) for sets is used to denote the ((symmetric difference)) (as i would expect).

((Edited 2013-01-24)).

=end

#14 Updated by Yui NARUSE over 1 year ago

  • Target version changed from 2.0.0 to next minor

#15 Updated by Zachary Scott 8 months ago

To put an end to the bikeshedding, and because I'd like this ticket to get a resolution:

The original request was to alias Hash#+ to Hash#merge.

matz, if you can make a decision on this alias it would be appreciated!

To all other requests for Hash, aliases and other methods please open a new ticket.

#16 Updated by Alex Chaffee 8 months ago

Operator overloading is for convenience and to "least surprise". Since + puts two numbers together, and + puts two strings together, and + puts two arrays together, + should also put two hashes together -- in the way that makes the most sense for each type.

#17 Updated by Yusuke Endoh 8 months ago

zzak (Zachary Scott) wrote:

matz, if you can make a decision on this alias it would be appreciated!

matz explicitly said that he was not positive:

https://bugs.ruby-lang.org/issues/6225#note-8

So it is reasonable to look for other aliases.

BTW, I don't think that Hash#merge is so frequently-used operation enough to have such a short notation.
I guess we may want it only when the values is not important, that is, when the hash is used like a set. In this case, we can use set.rb which provides Set#+.

Yusuke Endoh mame@tsg.ne.jp

#18 Updated by Jim Weirich 8 months ago

mame (Yusuke Endoh) wrote:

BTW, I don't think that Hash#merge is so frequently-used operation enough to have such a short notation.
I guess we may want it only when the values is not important, that is, when the hash is used like a set. In this case, we can use set.rb which provides Set#+.

I use Hash#merge a lot in a Rails project to manage valid attributes for testing scenarios. E.g. validdefaultattributes.merge(overriding_attributes).

-- Jim Weirich

#19 Updated by Alexey Muranov 8 months ago

There is another proposition, but for #reverse_merge: #7739.

Another relevant proposal: #7738.

#20 Updated by Eric Hodel 8 months ago

=begin
At DevelopersMeeting20130809 matz said:

17:26 charliesome: So it's rejected?
17:29 matz: I guess so.
17:29 matz: I still concern about merge is not a mere addition

So if the original reporter still wishes to make this a feature please make a slide with a short justification and a few examples for the next meeting.

=end

#21 Updated by Thomas Sawyer 8 months ago

=begin

So if the ((original reporter))...

That would be me, but I am not going to make a slide. Some one else can if they like. Personally I think it's obvious. #merge is one of the most commonly used methods of Hash, so having an operator for it, if at all possible, is a no-brainer in my opinion. Given the options, what other operator even comes close in meaning more so than #+? So merge is not commutative. Big deal. Technically neither is String#+.
=end

#22 Updated by Charlie Somerville 8 months ago

I think what matz means by "not a mere addition" is that in the cases of String#+ and Array#+, both operands are wholly represented by the result.

In Hash#merge, the return value might not be made up of whole of both operands. So it's not really an addition. IMO Hash#| might be appropriate, but I don't want to enter into a bikesheddy argument here.

#23 Updated by Charlie Somerville 8 months ago

  • Status changed from Assigned to Rejected

Also, since this specific feature (Hash#+) has been rejected by matz, I'm marking this ticket as rejected.

#24 Updated by Rodrigo Rosenfeld Rosas 8 months ago

I agree that the operation not being an addition is not a big deal. No one would expect it from a Hash. Groovy does have this operator working exactly as a merge and I don't see anyone complaining about it. But I don't see any problems either with calling it #|. I would be fine with either "hash + anotherhash" or "hash | anotherhash" as an alias for hash.merge(another_hash).

As a side note, yesterday I missed a method to perform Array#| in a way that work directly in the array (like #|! if it was possible). I had a code similar to this:

dependencies = Thread.current[:myappdependencies]
dependencies |= new
dependencies # won't work as expected, of course, since I wanted to store it under Thread.current[:myapp_dependencies]

But as I said, this is just an aside note, not related to this ticket.

#25 Updated by Rodrigo Rosenfeld Rosas 8 months ago

Charlie, any chances to reopen this ticket so that Matz could evaluate the usage of using | instead of +?

#26 Updated by Nobuyoshi Nakada 8 months ago

I think new method proposal should be a new ticket.

#27 Updated by Alexey Muranov 8 months ago

=begin
A use of Hash#| is proposed in #7739.

There is however a typo in the proposal: it should be

{ :a => 1, :b => 2 } | { :b => 1, :c => 2 } # => { :a => 1, :b => 2, :c => 2 }
=end

#28 Updated by Alexey Muranov 8 months ago

Just a thought about Hash#+: maybe it can be used for merging only hashes with disjoint sets of keys, and return nil otherwise?

#29 Updated by Yukihiro Matsumoto 8 months ago

I can imagine that would cause serious confusion among users. Raising exception might be better (but only just).

Matz.

#30 Updated by Prem Sichanugrist 8 months ago

I think this operator is really doesn't fit for Hash operation. While in a glance it might make sense, using #merge like what we're doing right now actually make more sense when you see the code. You're merging two hashes (dictionaries) together, not adding one hash (dictionary) to the other.

Also available in: Atom PDF