Reputation: 22237
I have a library class, which provides methods for equality and inequality. I'm deriving another class from this, which, differently from the parent class, introduces an ordering relation, i.e. it would make sense to ask for two elements of the derived class, which one is smaller. In particular, arrays of objects of the derived class can be sorted.
My first approach was
class MyClass < LibraryClass
def <(other)
...
end
def <=>(other)
return 0 if self == other
return -1 if self < other
return 1
end
# code for operators > <= >= is not shown here....
end
This seems to work, but I thought that it may be better to instead [sic] Comparable
, since this would give plenty of other methods for free.
The description of Comparable
says that I have to implement the <=>
operator, and other operators including ==
and !=
would then be automatically implemented. However, I am already happy with the ==
operator from the parent class, so no new method for equality should be generated.
I want to test for equality using the ==
operator from the parent class. If I implement the <=>
operator, and Comparable
implements an ==
operator in terms of my <=>
operator, I would end up in a recursive call.
For expression self == other
, how can I specify that the ==
operator of the parent class should be invoked?
Upvotes: 2
Views: 73
Reputation: 110685
First, let's create a subclass of Range that overwrites Range#==
.
class OddRange < Range
def ==(other)
!super
end
end
OddRange.new(1, 10) == OddRange.new(2, 7)
#=> true
Note that
OddRange.included_modules
#=> [Enumerable, Kernel]
which does not include Comparable
. Now let's create a subclass of OddRange
and examine it's behaviour.
class MyRange < OddRange
end
MyRange.ancestors
#=> [OddRange, Range, Enumerable, Object, Kernel, BasicObject]
MyRange.instance_method(:==).owner
#=> OddRange
rng1 = MyRange.new(1, 5)
rng2 = MyRange.new(2, 4)
rng3 = MyRange.new(1, 5)
rng1 == rng2
#=> true
rng1 == rng3
#=> false
We next include
the module Comparable
into MyRange
, and add an instance method to MyRange
1.
class MyRange
def <=>(other)
self.end <=> other.end
end
include Comparable
end
MyRange.ancestors
#=> [MyRange, MyComparable, OddRange, Range, Enumerable, Object, Kernel, BasicObject]
MyRange.instance_method(:==).owner
#=> Comparable
rng1 = MyRange.new(1, 5)
rng2 = MyRange.new(2, 4)
rng3 = MyRange.new(1, 5)
rng1 == rng2
#=> false
rng1 == rng3
#=> true
rng1 <=> rng2
#=> 1
No surprises.
If we don't want Comparable#==
to overwrite MyRange#==
, we could do the following.
class MyRange
def ==(other)
method(__method__).super_method.super_method.call(other)
end
end
This "jumps over" Comparable
and uses OddRange
's method :==
. See Method#super_method.
rng1 = MyRange.new(1, 5)
rng2 = MyRange.new(2, 4)
rng3 = MyRange.new(1, 5)
rng1 == rng2
#=> true
rng1 == rng3
#=> false
Now let's add another instance method to OddRange
.
class OddRange
def :<=(other)
(self.begin <=> self.begin) <= 0
end
end
rng1 = MyRange.new(1, 5)
rng2 = MyRange.new(2, 4)
rng1 <= rng2
#=> false
We see that Comparable#<=
and MyRange#<>
cause rng1 <= rng2
to return false
. If we wished to instead use OddRange#<=
, we could of course do what we did before, add
def <=(other)
method(__method__).super_method.super_method.call(other)
end
to MyRange
. More generally, if we don't want Comparable
instance methods to overwrite any OddRange
instance methods (which may change over time), we could do the following.
class MyRange
(instance_methods & OddRange.instance_methods(false)).each do |m|
define_method(m) do |other|
method(__method__).super_method.super_method.call(other)
end
end
end
rng1 = MyRange.new(1, 5)
rng2 = MyRange.new(2, 4)
rng3 = MyRange.new(1, 5)
rng1 == rng2
#=> true
rng1 == rng3
#=> false
rng1 <= rng3
#=> true
1 Redefining rng1
, rng2
and rng3
below isn't actually necessary.
Upvotes: 1
Reputation: 369478
The include
makes Comparable
the superclass of MyClass
and LibraryClass
the superclass of Comparable
. So, the implementation of ==
in Comparable
overrides the implementation of ==
in LibraryClass
.
What you can then do, is to override ==
again in MyClass
with a version that is identical to the one in LibraryClass
:
class MyClass < LibraryClass
include Comparable
def <=>(other)
# whatever
end
define_method(:==, LibraryClass.public_instance_method(:==))
end
Upvotes: 2