A fresh look at comparisons
Janice Caron
caron800 at googlemail.com
Mon Apr 14 01:18:42 PDT 2008
Every now again, someone (rightly) complains that opEquals should
return bool, not int. However, that's not my biggest complaint with
opEquals and opCmp. My biggest complaint is that it is very tedious
having to write code like:
class C
{
int x;
int opEquals(Object o)
{
C c = cast(C) o;
if (c is null)
throw new Exception("Cannot compare C with wrong type");
return x == c.x;
}
}
(and having to do so for every class I create). This is just wrong on
so many levels. It's wrong because we return int, not bool, yes. It's
wrong because opEquals should be a const function (it doesn't modify
this). It's wrong because it should accept a const object (it doesn't
modify it's input). But most of all, why should I have to do all the
work of making sure the input parameter is the right type?
What should I do if it's not? Should I return false? Should I throw an
exception? If I throw an exception, what type of exception should it
be? Is it OK to throw an object.Exception or should I roll my own kind
of Exception? It there some standard message I should be using for the
string? Does it matter that the message is in English and not
localized?
And it gets worse. For complex numbers we (correctly) have the following result:
cdouble x = 1;
cdouble y = 1i;
(x !<>= y) == true
Try doing that with a custom type! The problem is, opCmp() just isn't
powerful enough. With opCmp, there is no way to say "not less than,
not equal to, and not greater than".
I think it's time to take a fresh look at comparisons, and to realise
that comparison is /special/, in much the same way that a constructor
is special. It shouldn't be a regular function. It should be a special
function, with its own special syntax, and some extra special rules
that make it (1) easy to write, (2) logical, and (3) behave sensibly.
So with all that borne in mind, let's consider a new proposal to
/replace/ opEquals. (And later down the page, I shall suggest a
similar proposal to replace opCmp). Instead of:
class C
{
int x;
int opEquals(Object o)
{
C c = cast(C) o;
if (c is null)
throw new Exception("Cannot compare C with wrong type");
return x == c.x;
}
}
we do
class C
{
int x;
is(this == c)
{
return x == c.x;
}
}
Because this is no longer a regular function, the compiler can take
care of all the casting and exception throwing, so we don't have to.
Moreover, it is now possible for the compiler to generate a *default*
equality comparison, if we don't supply one. The default equality
comparison would be recursive memberwise equality comparison (which,
after all, is usually the correct thing thing to do).
And /of course/, the function should return bool, and should consider
both "this" and "c" to be both of the same type, and both const.
Likewise, we replace opCmp with:
class C
{
int x;
is(this < c)
{
return x < c.x;
}
}
The default implementation of is(this < c) should be to return false
always, which implies that comparison is not meaningful for this type.
The compiler can auto-generate all fourteen possible comparisons using
only these two functions, as follows:
(a == b) == (a == b)
(a < b) == (a < b)
(a <= b) == (a == b || a < b)
(a <> b) == (a < b || b < a)
(a <>= b) == (a == b || a < b || b < a)
(a > b) == (b < a)
(a >= b) == (a == b || b < a)
(a != b) == (!(a == b))
(a !<> b) == (!(a < b || b < a))
(a !<>= b) == (!(a == b || a < b || b < a))
(a !< b) == (!(a < b))
(a !<= b) == (!(a == b || a < b))
(a !> b) == (!(b < a))
(a !>= b) == (!(a == b || b < a))
We could allow the programmer to supply some or all of these, if they
so desire, for reasons of efficiency,
To my mind, this proposal fixes everything that is wrong with comparison in D.
More information about the Digitalmars-d
mailing list