Another trap is redefining #== without also looking at #eql?, which means Hash doesn't behave like you expect. It's just another bit of mental trivia you've got to Just Know...
Another trap is redefining #== without also looking at #eql?, which means Hash doesn't behave like you expect. It's just another bit of mental trivia you've got to Just Know...
Lisp has 'eq', which is for object identity. Lisp has 'equal' which checks for structural equality.
The default `==` from Object compare identity, so unless you define it `Money.new(20, 'USD') != Money.new(20, 'USD')`.
Don't try to change or complain about Ruby, you are likely more happy with languages like go.
That's not the issue at all. Abstractions are great. Redefining operators is not abstraction, it's practically obfuscation.
The fact that it's an operator or a method doesn't change a thing. For example in Java many classes redefine the `equals` method. It's default behavior is just like Ruby, comparing identity. It's not an operator but the effect is exactly the same. And IMO it's worse because now you have a leaky abstraction with types you need to compare with `==` and others with `.equals`.
It's just you who have this expectation of operators being not redefinable. When I read Ruby code, for me `==`, or `+` are just regular methods like any others with just a bit of syntactic sugar.
It also allows for greater polymorphism. Like the Money object from before. If I couldn't redefine `+`, then `[Money.new(20), Money.new(22)].sum` wouldn't work.
Or Money.new(20, 'USD') == Money.new(125, 'CNY') when ExchangeRateManager.getExchangeRate('CNY', 'USD') == 0.16?
My point is that when performing these comparisons, it may be useful to use a more descriptive function like:
boolean currenciesHaveSameWorth(Money m1, Money m2)
And then a reader of the calling code might not have to look into the implementation to understand what the function is doing, whereas you definitely would when using == because == now means "whatever it's overridden to mean"
Like someone else pointed out in this thread, designing APIs require consistency and good taste.
If I had to implement this API you code above would evaluate to `ArgumentError unknown currency "US Cents"`.
> Or Money.new(20, 'USD') == Money.new(125, 'CNY') when ExchangeRateManager.getExchangeRate('CNY', 'USD') == 0.16?
Again, me designing this API, it wouldn't be equal. Why? For the same reason `1 != "1"`, if you cast them, yes they are equal, but implicit casting (aka weak typing) is not idiomatic in Ruby, it's possible, but very rare.
> boolean currenciesHaveSameWorth(Money m1, Money m2)
At this point you might as well do `m1 == m2.convert_to(m1.currency)`, because "HaveSameWorth" might mean many different things too.
I personally hate that last style because it's obvious that the "HaveSameWorth" relation is intended to be symmetric, and by writing it like m1 == m2.convert(...) you're prefering one side over the other. It looks bad for me :).
Also, in case of real-life objects it makes sense to spell out what do you mean by 'equality' (or 'equivalency'), and leave the default implementation to represent the philosophical concepts of "the same" and "equivalent to".
For example BigDecimal. With operator overloading, I can easily compare a BigDecimal object to an integer or float, the same way I can already compare them to each other:
BigDecimal.new("10.0") == 10.0
10 == 10.0
Without operator overloading, this would become needlessly messy (and require explicit handling of nils): d = BigDecimal.new("10.0")
!d.nil? && d.value_equal_to(10.0)
Ruby has always been about readability of the code, and avoiding unnecessary repetition, and I think operator overloading (when used correctly) is a great example of this.