Feature #6225

Hash#+

Added by Thomas Sawyer over 3 years ago. Updated about 2 years ago.

[ruby-core:43904]
Status:Rejected
Priority:Normal
Assignee:Yukihiro Matsumoto

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


Related issues

Duplicated by Ruby trunk - Feature #9778: Bring shortcut methods to Hash Rejected 04/27/2014

History

#1 Updated by Yusuke Endoh over 3 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 over 3 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 over 3 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 over 3 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 over 3 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 over 3 years ago

Sorry for my bad English, I didn'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 concatenate 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 over 3 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 over 3 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 over 3 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 concatenate 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 over 3 years ago

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

#11 Updated by Thomas Sawyer over 3 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 over 3 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 over 3 years ago

How about Hash#| for Hash#reverse_merge, and Hash#|= for Hash#reverse_merge! 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.

#14 Updated by Yui NARUSE almost 3 years ago

  • Target version changed from 2.0.0 to next minor

#15 Updated by Zachary Scott about 2 years 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 about 2 years 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 about 2 years 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 about 2 years 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. valid_default_attributes.merge(overriding_attributes).


-- Jim Weirich

#19 Updated by Alexey Muranov about 2 years ago

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

Another relevant proposal: #7738.

#20 Updated by Eric Hodel about 2 years ago

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.

#21 Updated by Thomas Sawyer about 2 years ago

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#+.

#22 Updated by Charlie Somerville about 2 years 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 about 2 years 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 about 2 years 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 + another_hash" or "hash | another_hash" 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[:_my_app_dependencies]
dependencies |= new_dependencies # won't work as expected, of course, since I wanted to store it under Thread.current[:_my_app_dependencies]

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

#25 Updated by Rodrigo Rosenfeld Rosas about 2 years ago

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

#26 Updated by Nobuyoshi Nakada about 2 years ago

I think new method proposal should be a new ticket.

#27 Updated by Alexey Muranov about 2 years ago

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 }

#28 Updated by Alexey Muranov about 2 years 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 about 2 years 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 about 2 years 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.

#31 Updated by Nobuyoshi Nakada over 1 year ago

#32 Updated by Nobuyoshi Nakada over 1 year ago

  • Related to deleted (Feature #9778: Bring shortcut methods to Hash)

#33 Updated by Nobuyoshi Nakada over 1 year ago

  • Duplicated by Feature #9778: Bring shortcut methods to Hash added

Also available in: Atom PDF