Equality Methods
bearophile
bearophileHUGS at lycos.com
Tue Jun 9 07:35:26 PDT 2009
I have already asked about this topic here months ago, but now I know a bit more OOP, so I can raise the bar for myself a bit. So now I consider class inheritance too. I am trying to write "correct" equality Methods.
I have read this nice article about equality in Java, the things it says can be used equally in D too:
http://www.artima.com/lejava/articles/equality.html
Even comparing really simple objects can be not easy to do.
So do you see troubles/ bugs/ things that can be improved in the following code? (D1, Phobos, putr is writefln):
import std.string: format;
import std.stdio: putr = writefln;
/// Used by all exceptions
template ExceptionTemplate() {
this() {
super(this.classinfo.name);
}
this(string msg) {
super(this.classinfo.name ~ ": " ~ msg);
}
}
/// Thrown by user-defined opCmp() of classes
class UncomparableException: Exception {
mixin ExceptionTemplate;
}
class Point {
// in practice for this purpose, instead of re-defining opEquals,
// opCmp, opHash, etc, I use a Record (Tuple in Phobos2).
alias typeof(this) TyThis;
private final int x, y;
this(int x, int y) {
this.x = x;
this.y = y;
}
public int opEquals(Object other) {
auto that = cast(TyThis)other;
return that !is null && that.canEqual(this) &&
this.x == that.x && this.y == that.y;
}
public bool canEqual(Object other) {
return cast(TyThis)other !is null; // ?
}
public hash_t toHash() {
return (41 * (41 + this.x) + this.y); // ugly, but it doesn't matter now
}
public int opCmp(Object other) {
auto that = cast(TyThis)other;
if (that is null || !that.canEqual(this)) // ?
throw new UncomparableException("");
if (this.x == that.x)
return this.y - that.y;
else
return this.x - that.x;
}
public string toString() {
return format("Point(%d, %d)", this.x, this.y);
}
}
enum Color { RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET }
public class ColoredPoint : Point {
alias typeof(this) TyThis;
private final Color color;
this(int x, int y, Color color) {
super(x, y);
this.color = color;
}
// define this if you want this uncomparable to the base class
override public bool canEqual(Object other) {
return cast(TyThis)other !is null; // ?
}
override public int opEquals(Object other) { // bug: not symmetric
auto that = cast(TyThis)other;
return that !is null && that.canEqual(this) &&
this.color == that.color && super.opEquals(that); // ?
}
override public int opCmp(Object other) {
auto that = cast(TyThis)other;
if (that is null || !that.canEqual(this)) // ?
throw new UncomparableException("");
// coordinates are more important than color
int super_cmp = super.opCmp(that);
if (super_cmp == 0)
return this.color - that.color;
else
return super_cmp;
}
public string toString() {
return format("Point(%d, %d)", this.x, this.y);
}
}
class Point2 : Point {
this(int x, int y) {
super(x, y);
}
}
class Point3 : Point {
alias typeof(this) TyThis;
this(int x, int y) {
super(x, y);
}
public bool canEqual(Object other) {
return cast(TyThis)other !is null; // ?
}
}
void main() {
auto p1 = new Point(1, 1);
auto p2 = new Point(1, 1);
auto p3 = new Point(1, 2);
putr(p1 == p2, " ", p2 == p1); // 1 1
putr(p1 == p3, " ", p3 == p1); // 0 0
putr(p2 == p3, " ", p3 == p2); // 0 0
int[Point] aa = [p1:1, p2:2, p3:3];
putr(aa);
auto p4 = new Point(1, 0);
auto p5 = new Point(2, 0);
auto p6 = new Point(0, 2);
putr(p1 > p3, " ", p3 > p1); // false true
putr(p1 > p4, " ", p4 > p1); // true false
putr(p1 > p5, " ", p5 > p1); // false true
putr(p1 > p6, " ", p6 > p1); // true false
putr();
Point p8 = new Point(1, 2);
ColoredPoint cp1 = new ColoredPoint(1, 2, Color.RED);
putr(p8 == cp1); // prints 0
putr(cp1 == p8); // prints 0
//putr(p8 > cp1); // uncomparable exception
//putr(p8 > cp1); // uncomparable exception
//putr(p8 < cp1); // uncomparable exception
//putr(cp1 < p8); // uncomparable exception
//putr(cp1 > p8); // uncomparable exception
putr();
putr(cast(Point)p1 !is null, " ", cast(Point)cp1 !is null); // true true
putr(cast(ColoredPoint)p1 !is null, " ", cast(ColoredPoint)cp1 !is null); // false true
putr(p1.classinfo == p2.classinfo, " ", p1.classinfo == p6.classinfo); // 1 1
putr(cp1.classinfo == p1.classinfo, " ", p1.classinfo == cp1.classinfo); // 0 0
putr();
auto pp1 = new Point2(1, 1);
putr(p1 == pp1); // 1
putr(p1 > pp1, " ", p1 < pp1); // false false
auto pp2 = new Point3(1, 1);
putr(p1 == pp2); // 0
//putr(p1 > pp2, " ", p1 < pp2); // uncomparable exception
}
opCmp is necessary in D even for associative arrays because hash collisions are resolved with a search tree that needs opCmp. If D AAs become simpler, and adopt a simpler collision management strategy (see for example Python dicts, or C# hash maps), the opCmp isn't necessary anymore, and the code in this page gets simpler.
Some people in the comments of that article talk about an hypothetical Equalator interface, but I am not expert enough yet about OOP to tell if the Equalator idea is good.
Bye and thank you,
bearophile
More information about the Digitalmars-d-learn
mailing list