elbows
10/1/2003 5:13:00 PM
"Robert Klemme" <bob.news@gmx.net> wrote in message news:<ble00n$b7qp2$2@ID-52924.news.uni-berlin.de>...
> "Ben Giddings" <bg-rubytalk@infofiend.com> 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