From: elbows@... (Nathan Weston) Date: 2003-10-02T02:21:44+09:00 Subject: Re: Making == symmetric? "Robert Klemme" wrote in message news:... > "Ben Giddings" schrieb im Newsbeitrag > news:3F7A4EBC.20007@infofiend.com... > > Nathan Weston wrote: [snip] > > > > Maybe instead: > > > > def compare(a, b) > > retval = false > > if a.can_compare_to?(b) > > retval = a.==(b) > > end > > if retval and b.can_compare_to?(a) > > retval = b.==(a) > > end > > > > return retval > > end > > Doesn't solve the overhead problem. > > What one would really need is multiple dispatch: > > module Kernel > def self.register_compare(classA, classB, &comp) > ( @comparisons ||= {})[ [classA, classB] ] = comp > @comparisons[ [classB, classA] ] = proc { |b,a| comp.call(a, b) } > end > > def self.compare(a, b) > c = @comparisons[ [a.class, b.class] ] > c.nil? ? a == b : c.call( a, b ) > end > end > > > class Foo > Kernel.register_compare( self, self ) do |a,b| > puts "Foo == Foo" > false > end > > def ==(b) > Kernel.compare( self, b ) > end > end > > > class Bar < Foo > Kernel.register_compare( self, self ) do |a,b| > puts "Bar == Bar" > false > end > > Kernel.register_compare( self, Foo ) do |a,b| > puts "Bar == Foo" > false > end > end > > f1 = Foo.new > f2 = Foo.new > > f1 == f2 > > b1 = Bar.new > b2 = Bar.new > > b1 == b2 > > b1 == f1 > f1 == b1 > > > But this has several disadvantages, too: You keep creating all those > temporary arrays and you have the need for O(n**2) comparison operators. > > Regards > > robert That seems a bit too messy to me. Just overriding == is cleaner and more intuitive. How about: def compare(a, b) if a.can_compare_to?(b) return a.==(b) elsif b.can_compare_to?(a) return b.==(a) end return false end I think the problem Ben's proposal is trying to avoid is something like this: class Foo def can_compare_to?(obj) if obj.is_a?(Bar) return true else ... end #Foo thinks that it is not equal to any Bar object def ==(obj) if obj.is_a?(Bar) return false else ... end end class Bar def can_compare_to?(obj) if obj.is_a?(Foo) return true else ... end #Bar thinks that it is equal to any Foo object def ==(obj) if obj.is_a?(Foo) return true else ... end end Under Ben's solution: Foo.new == Bar.new #false Bar.new == Foo.new #false So, the writer of Foo is happy, because he always gets the answer he wanted. But the writer of Bar is unhappy, because he wanted true in both cases. Under my proposal Foo.new == Bar.new #false Bar.new == Foo.new #true The writers of Foo and Bar are both unhappy, because they get the wrong result in one case. However, I would argue that, under both Ben's proposal and my proposal, you get broken behavior -- it's just broken in different ways. Either way, to get non-broken behavior, you need the writers of Foo and Bar to come to some agreement about how their classes are compared (which I don't think is unreasonable). And my way only does one comparison, instead of 2. Also, I suggest this default implentation of can_compare_to? class Object def can_compare_to?(obj) return obj.kind_of?(self.class) end end This way, in the common case where objects of class A are only equal to other objects of class A, there is no need to override can_compare_to? The main problem I still see is the need to override can_compare_to? at all. If this change is implemented, I expect many people will override #== and be confused when this has no effect. Maybe there is a way for #== to return an "I don't know" result, which could remove the need for can_compare_to? at all. Newbies could still break things by writing == methods that return false as a default (instead of "I don't know"), but this would really only be a problem if they were writing class libraries for someone else's consumption (which hopefully they wouldn't be doing). Nathan