comparison operators
Kevin Bealer
kevinbealer at gmail.com
Wed Apr 16 10:47:15 PDT 2008
Here is another strategy for comparisons which I think satisfies several currently unusual criteria. It's based on the idea of "Schwartz sorting". I'll give the pseudo code first:
class Object {
byte[] schwartzKey()
{
throw new UnimplementedMethodException("schwartzKey");
}
size_t getHash()
{
return Object.standardHash(schwartzKey());
}
int opEquals(Object other) // could return bool
{
return opCmp(other);
}
int opCmp(Object other)
{
return schwartzKey() < other.schwartzKey();
}
};
(Note: I've left the constness off all these methods to hopefully simplify the discussion.)
The schwartzKey method returns a key whose comparison produces the desired ordering for this type. If you have an ASCII string and want locale "C" ordering, this could be as simple
as casting that string to a byte array. If you only want to order objects for the sake of hash
tables, it could just be a "slice" of what would otherwise be the 4 byte cached "hash" value
of the fields. The name comes from the idea of the Schwartzian transform used for sorting:
http://en.wikipedia.org/wiki/Schwartzian_transform. (But please suggest a better one! Maybe opComparisonKey().)
In the above design, if you define the "schwartzKey" method, you already have reasonable, correct, and consistent definitions for opCmp, opEquals, and getHash.
On the other hand, if you just want field-wise comparisons, you can just redefine opCmp. If you only want to allow equality comparisons, just define opEquals. You can also just make the object hashable without defining any of the other functions.
And if you define nothing, the comparisons above will just throw an exception.
ALSO: this solves the "Fundametal OO Problem" that objects of different types are not comparable (as described in Joshua Bloch's Effective Java). The problem is: depending
on which class you have on the left side, its impossible to write comparison operators
that respect the class of both objects.
class A {
int opCmp(Object o) {
//compares "this" to o -- but doesn't know about "n"
}
};
class B : A {
int n;
int opCmp(Object o) {
//compares "this" to o -- but doesn't know classes derived
// from B at some later time.
}
};
Since the author of A doesn't know about B, its normally impossible to write comparators
for A that produce correct and consistent results for both A < B and B < A.
But with the schwartzKey method, both comparisons work properly, since A will see the
value of "n" in B's schwartzKey.
class A {
// int opCmp(); -- not defined, default is fine
byte[] schwartzKey(Object o)
};
class B : A {
int n;
// int opCmp(); -- not defined, default is fine
byte[] schwartzKey(Object o)
{
return super.schwartzKey() ~ cast(ubyte[]) toString(n);
}
};
But you can still safely override each of these various other methods as long as you
know that your class is not going to be someone else's base class.
The best part is -- this whole feature is just a library change -- nothing in the language
itself has to change at all, just some mostly compatible changes to Object.
If you want backward compatibility, you need to catch the "unimplemented method"
exception in each of these methods and do something slightly different for methods
that currently have non-throwing definitions. (I think I prefer the throwing definitions,
but I'm not picky about that part of it.)
Kevin
(P.S. Use the Schwartz!)
More information about the Digitalmars-d
mailing list