digitalmars.D.learn - Equality Methods
- bearophile (143/143) Jun 09 2009 I have already asked about this topic here months ago, but now I know a ...
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 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
Jun 09 2009