A fresh look at comparisons
Scott S. McCoy
tag at cpan.org
Mon Apr 14 11:00:04 PDT 2008
I like this, except for the semantic irregularities similar to Scala
we'd be introducing by not having the operator evaluated.
Also, if you can't cast successfully I'd think you would simply return
false for opEquals.
if o !is C, then they can't possibly be equal. ;-)
The idea behind opEquals acting this way, I assume naturally, is that it
enables the ability for your type to know how to compare itself to
multiple *other* types which exist. Somewhat similar to how you can
freely compare a long and an int in many cases, without ramifications or
need to know any bit-wise or type difference. So what is the answer?
What makes two types comparable? Common inheritance? Where? How do I
specify (if all I'm saying is "is(this == c)" the possible types I can
be compared to within my system. What if I know these types, and they
are not my descendants? Or if they are my descendants, when and how can
I say that my descendant is not comparable to it's parent?
Cheers,
Scott S. McCoy
On Mon, 2008-04-14 at 09:18 +0100, Janice Caron wrote:
> 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