View mode: basic / threaded / horizontal-split · Log in · Help
April 17, 2008
Re: A fresh look at comparisons
On 16/04/2008, Yigal Chripun <yigal100@gmail.com> wrote:
> OK, I see your point.

Ta. But you see one of my points. I made more than one.


>  If I understood this correctly, the issue is the implicit cast to super.

This particular issue could be dealt with as you suggest, but there
are other issues that it doesn't fix. For example - consider how you
might write an opCmp for complex numbers.

   class Complex
   {
       int opCmp(Object o)
       {
           Complex c = cast(Complex)o;
           if (c is null) return false;

           if (this.im == 0 && c.im == 0)
           {
               if (this.re < c.re) return -1;
               else if (this.re == c.re) return 0;
               else return 1;
           }
           else return ????
       }
   }

The problem is that complex numbers become UNORDERED when the
imaginary part (of either number) becomes non-zero. opCmp() has no way
to express that. Henning Hasemann did suggest making opCmp return an
enum, instead of an int, so that all four possible returns could be
indicated, but again, like you, he's solving one problem at a time.
The whole "fresh look" approach is to solve all of the problems in one
fell swoop. So, this particular problem would be solved in my scheme
with

   class Complex
   {
       is(this < c, unordered)
       {
           if (this.im == 0 && c.im == 0)
           {
               return this.re < c.re;
           }
           else return false;
       }
   }

And there is /yet/ another problem to consider - your "explicit"
approach works just fine for opCmp, but it won't work for opEquals.
The reason is that the default behavior we desire from opCmp is very
different from the default behavior of opEquals. Consider:

   class A { ... }
   class B : A { int x; }

   // Your way
   int opEquals(explicit A a1, explicit A a2);

   B b = new B;
   B c = new C;

   if (b == c)...

Under your scheme, b would not be comparable for equality with c,
since there is no explicit equality test.

By contrast, my scheme would generate a default equality test.
Specifically, b would be considered equal to c if and only if

   (cast(A)b == cast(A)c) && (b.x == c.x)

Of course, in the both schemes, the programmer can always override the
default, by defining an equals comparison for B, but nonetheless, my
scheme gets you a fine default, which is likely to be the right thing
to do much, if not most, of the time.

In other words, I have presented a comprehensive solution which solves
many problems at once, not just a single problem. (Maybe that's why I
have trouble explaining it?)
April 17, 2008
Re: A fresh look at comparisons
Janice Caron wrote:
> On 16/04/2008, Yigal Chripun <yigal100@gmail.com> wrote:
>   
>> OK, I see your point.
>>     
>
> Ta. But you see one of my points. I made more than one.
>
>
>   
>>  If I understood this correctly, the issue is the implicit cast to super.
>>     
>
> This particular issue could be dealt with as you suggest, but there
> are other issues that it doesn't fix. For example - consider how you
> might write an opCmp for complex numbers.
>
>     class Complex
>     {
>         int opCmp(Object o)
>         {
>             Complex c = cast(Complex)o;
>             if (c is null) return false;
>
>             if (this.im == 0 && c.im == 0)
>             {
>                 if (this.re < c.re) return -1;
>                 else if (this.re == c.re) return 0;
>                 else return 1;
>             }
>             else return ????
>         }
>     }
>
> The problem is that complex numbers become UNORDERED when the
> imaginary part (of either number) becomes non-zero. opCmp() has no way
> to express that. Henning Hasemann did suggest making opCmp return an
> enum, instead of an int, so that all four possible returns could be
> indicated, but again, like you, he's solving one problem at a time.
> The whole "fresh look" approach is to solve all of the problems in one
> fell swoop. So, this particular problem would be solved in my scheme
> with
>
>     class Complex
>     {
>         is(this < c, unordered)
>         {
>             if (this.im == 0 && c.im == 0)
>             {
>                 return this.re < c.re;
>             }
>             else return false;
>         }
>     }
>   

the above is a non-issue IMO. personally I HATE error return codes/enums
(that's a C idiom and does _not_ belong in a modern language like D). (a
< b) is an error for unordered types just like  "hello" + 2 is an error.
nothing should be returned is in such a case it doesn't matter if it's
an int or an enum. This is an error and should be dealt as such - either
throw an exception or don't define an opCmp for complex (I prefer the
latter since as you said so yourself, Complex numbers are unordered).
I've previously said that opCmp should not be in Object and should not
be defined for all types. a better design IMO is to have an interface
"Ordered" and maybe also "PartialyOrdered" too. also, in conjunction
with my previous post, they could be defined as annotations instead just
like "explicit".

as an aside, annotations in Java use almost the same syntax as
interfaces. This makes perfect sense since annotations have a lot in
common with interfaces. annotations are special interfaces that provide
additional _non_override-able_ functionality so that all subtypes will
have that added functionality. that means that they can mostly be
implemented today in D for classes. a little bit of syntactic sugar and
support for primitives is all that is needed to have this feature in D.

> And there is /yet/ another problem to consider - your "explicit"
> approach works just fine for opCmp, but it won't work for opEquals.
> The reason is that the default behavior we desire from opCmp is very
> different from the default behavior of opEquals. Consider:
>
>     class A { ... }
>     class B : A { int x; }
>
>     // Your way
>     int opEquals(explicit A a1, explicit A a2);
>
>     B b = new B;
>     B c = new C;
>
>     if (b == c)...
>
> Under your scheme, b would not be comparable for equality with c,
> since there is no explicit equality test.
>
> By contrast, my scheme would generate a default equality test.
> Specifically, b would be considered equal to c if and only if
>
>     (cast(A)b == cast(A)c) && (b.x == c.x)
>
> Of course, in the both schemes, the programmer can always override the
> default, by defining an equals comparison for B, but nonetheless, my
> scheme gets you a fine default, which is likely to be the right thing
> to do much, if not most, of the time.
>   
I'm not sure that this default should be provided. the default for many
languages is that unless you define an opEquals for your type, the
comparison will default to identity comparison and from a reply to a
previous post of mine, apparently this is also true in D.
this is the only sensible default IMO. The problem I see with the
suggested default above is the case for classes that should not be
compared at all.
Do i override and throw an error, or should I use:
bool opEquals(A a, A b) {
 if (a is b) return true;
 return false;
}
there are only two sensible approaches IMHO: unless defined the compiler
replaces "==" with "is" (this will never be wrong) or an error is
produced since there is no explicit equality test. If this is not what I
wanted then the error will remind me that I need to provide the test myself.
OTOH, your solution is very similar to C++ which provides defaults which
not always suit the needs of the programmer which then the programmer
needs to know/remember to override. That makes C++ that much more
difficult for beginners to grok an I've admittedly have been bitten by
that myself.
> In other words, I have presented a comprehensive solution which solves
> many problems at once, not just a single problem. (Maybe that's why I
> have trouble explaining it?)
>   
your suggestion is indeed a comprehensive solution to a very specific
problem. OTOH I'm trying to suggest a more general approach that could
be used generally in D without special treatment syntactically for just
this problem. Basically, my trouble understanding your suggestion is
that I don't see a need for that special treatment as opposed to a more
general solution.
April 17, 2008
Re: A fresh look at comparisons
Yigal Chripun Wrote:

> >
> > The problem is that complex numbers become UNORDERED when the
> > imaginary part (of either number) becomes non-zero. opCmp() has no way
> > to express that. Henning Hasemann did suggest making opCmp return an
> > enum, instead of an int, so that all four possible returns could be
> > indicated, but again, like you, he's solving one problem at a time.
> > The whole "fresh look" approach is to solve all of the problems in one
> > fell swoop. So, this particular problem would be solved in my scheme
> > with
> >
> >     class Complex
> >     {
> >         is(this < c, unordered)
> >         {
> >             if (this.im == 0 && c.im == 0)
> >             {
> >                 return this.re < c.re;
> >             }
> >             else return false;
> >         }
> >     }
> >   
> 
> the above is a non-issue IMO. personally I HATE error return codes/enums
> (that's a C idiom and does _not_ belong in a modern language like D). (a
> < b) is an error for unordered types just like  "hello" + 2 is an error.
> nothing should be returned is in such a case it doesn't matter if it's
> an int or an enum. This is an error and should be dealt as such - either
> throw an exception or don't define an opCmp for complex (I prefer the
> latter since as you said so yourself, Complex numbers are unordered).
> I've previously said that opCmp should not be in Object and should not
> be defined for all types. a better design IMO is to have an interface
> "Ordered" and maybe also "PartialyOrdered" too. also, in conjunction
> with my previous post, they could be defined as annotations instead just
> like "explicit".
> 

Slightly OT -- I realize the Complex class is being used as an example to prove a point and this is not a discussion on the mathematics of complex numbers. However --

There are instances when an unordered field (i.e. complex numbers) may be considered to be ordered when reduced to an underlying field (i.e. real numbers), but I don't think it's good practice to build that exception into the definition. In other words the Complex class should always return false on  comparison since it is always unordered.

If an exception is allowed for the case where the imaginary parts are zero, why not when the real parts are zero? 0+3i is clearly ordered with respect to 0+4i. In fact any time the real OR imaginary parts are equal the corresponding parts are ordered. This is nothing more than ordering points on any constant imaginary or constant real line in the complex plane. IMHO a better case could be made for ordering complex numbers by comparing their norms, with the obvious deficit that two complex numbers with the same order would not necessarily be equal.

I recognize that there may be applications where, for good reason, real and complex numbers are mixed and that comparison of real numbers with real parts of complex numbers is needed. But I don't think that complex numbers are a good example of a "sometimes ordered" system and I'm not sure that such a system exists. opCmp() for any unordered system should always return false.

Paul
April 17, 2008
Re: A fresh look at comparisons
Paul D Anderson Wrote:

> Yigal Chripun Wrote:
> 
> > >
> > > The problem is that complex numbers become UNORDERED when the
> > > imaginary part (of either number) becomes non-zero. opCmp() has no way
> > > to express that. Henning Hasemann did suggest making opCmp return an
> > > enum, instead of an int, so that all four possible returns could be
> > > indicated, but again, like you, he's solving one problem at a time.
> > > The whole "fresh look" approach is to solve all of the problems in one
> > > fell swoop. So, this particular problem would be solved in my scheme
> > > with
> > >
> > >     class Complex
> > >     {
> > >         is(this < c, unordered)
> > >         {
> > >             if (this.im == 0 && c.im == 0)
> > >             {
> > >                 return this.re < c.re;
> > >             }
> > >             else return false;
> > >         }
> > >     }
> > >   
> > 
> > the above is a non-issue IMO. personally I HATE error return codes/enums
> > (that's a C idiom and does _not_ belong in a modern language like D). (a
> > < b) is an error for unordered types just like  "hello" + 2 is an error.
> > nothing should be returned is in such a case it doesn't matter if it's
> > an int or an enum. This is an error and should be dealt as such - either
> > throw an exception or don't define an opCmp for complex (I prefer the
> > latter since as you said so yourself, Complex numbers are unordered).
> > I've previously said that opCmp should not be in Object and should not
> > be defined for all types. a better design IMO is to have an interface
> > "Ordered" and maybe also "PartialyOrdered" too. also, in conjunction
> > with my previous post, they could be defined as annotations instead just
> > like "explicit".
> > 
> 
> Slightly OT -- I realize the Complex class is being used as an example to prove a point and this is not a discussion on the mathematics of complex numbers. However --
> 
> There are instances when an unordered field (i.e. complex numbers) may be considered to be ordered when reduced to an underlying field (i.e. real numbers), but I don't think it's good practice to build that exception into the definition. In other words the Complex class should always return false on  comparison since it is always unordered.
> 
> If an exception is allowed for the case where the imaginary parts are zero, why not when the real parts are zero? 0+3i is clearly ordered with respect to 0+4i. In fact any time the real OR imaginary parts are equal the corresponding parts are ordered. This is nothing more than ordering points on any constant imaginary or constant real line in the complex plane. IMHO a better case could be made for ordering complex numbers by comparing their norms, with the obvious deficit that two complex numbers with the same order would not necessarily be equal.
> 
> I recognize that there may be applications where, for good reason, real and complex numbers are mixed and that comparison of real numbers with real parts of complex numbers is needed. But I don't think that complex numbers are a good example of a "sometimes ordered" system and I'm not sure that such a system exists. opCmp() for any unordered system should always return false.
> 
> Paul

Sorry -- the first snippet (now with ">>>") is from Janice, not Yigal.
April 25, 2008
Re: A fresh look at comparisons
Steven Schveighoffer wrote:
> 
> The default opCmp today is:
> 
> int opCmp(Object o) { return this !is o;}
> 

It is not. For DMD 2.012, it is:

    int opCmp(Object o)
    {
	// BUG: this prevents a compacting GC from working, needs to be fixed
	//return cast(int)cast(void *)this - cast(int)cast(void *)o;

	throw new Error(cast(string) ("need opCmp for class "
                                      ~ this.classinfo.name));
    }


It's also the same in DMD 1.023 (except without the string cast).

-- 
Bruno Medeiros - MSc in CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
April 25, 2008
Re: A fresh look at comparisons
Janice Caron wrote:
> 
> 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.
> 

You say:
" 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 don't mean offense, but I find such idea ridiculous! opCmp is a 
mechanism for defining the ordering in types that have have full 
ordering. What's the problem there? Why should it be extended to support 
something more? Your proposal added more complicated syntax and 
semantics to D to support comparison for types that don't have full 
ordering (like complex numbers), but such use cases are very specific 
and uncommon. And that's why I find the idea of complicating the 
language to support them very unlikable.
Furthermore, while the semantics of full ordering are the same for all 
types that support it, the semantics of partial ordering might not be. 
For example, the comparison operators for complex types might not be 
quite applicable for other kinds of partial ordering mathematical types 
(I wish I was a mathematician so I could remember a concrete example).
No, this is the kind of stuff that should be handled with plain member 
functions.

And this same logic applies to complex types themselves: I think that 
complex types (and their specific operators) should be a library type 
instead of a built-in type, and their specific comparison operators 
should be member functions. Walter has been thinking about this, and 
hopefully he will go forward with it.

-- 
Bruno Medeiros - MSc. in CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
April 25, 2008
Re: A fresh look at comparisons
Bruno Medeiros wrote:
> Janice Caron wrote:
>>
>> 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.
>>
> 
> You say:
> " 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 don't mean offense, but I find such idea ridiculous! opCmp is a 
> mechanism for defining the ordering in types that have have full 
> ordering. What's the problem there? Why should it be extended to support 
> something more? Your proposal added more complicated syntax and 
> semantics to D to support comparison for types that don't have full 
> ordering (like complex numbers), but such use cases are very specific 
> and uncommon. And that's why I find the idea of complicating the 
> language to support them very unlikable.
> Furthermore, while the semantics of full ordering are the same for all 
> types that support it, the semantics of partial ordering might not be. 
> For example, the comparison operators for complex types might not be 
> quite applicable for other kinds of partial ordering mathematical types 
> (I wish I was a mathematician so I could remember a concrete example).
> No, this is the kind of stuff that should be handled with plain member 
> functions.
> 
> And this same logic applies to complex types themselves: I think that 
> complex types (and their specific operators) should be a library type 
> instead of a built-in type, and their specific comparison operators 
> should be member functions. Walter has been thinking about this, and 
> hopefully he will go forward with it.
> 

I have just read your new thread "A Fresh Look at Comparisons, Take 2" 
(I've started reading 1400 late posts in digitalmars.D alone, so it's a 
bit confusing...) , and it seems that you converged to this same opinion:


"[...] Library solutions such as
std.algorithm may be able to accept partially ordered sets where
appropriate. But I think that it's such a rare edge-case that having
it built into the language itself is probably unnecessary. After all,
we don't have that now, and I've never seen a complaint about its
absence."

-- 
Bruno Medeiros - Software Developer, MSc. in CS/E graduate
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
April 25, 2008
Re: A fresh look at comparisons
"Bruno Medeiros" wrote
> Steven Schveighoffer wrote:
>>
>> The default opCmp today is:
>>
>> int opCmp(Object o) { return this !is o;}
>>
>
> It is not. For DMD 2.012, it is:
>
>     int opCmp(Object o)
>     {
> // BUG: this prevents a compacting GC from working, needs to be fixed
> //return cast(int)cast(void *)this - cast(int)cast(void *)o;
>
> throw new Error(cast(string) ("need opCmp for class "
>                                       ~ this.classinfo.name));
>     }
>
>
> It's also the same in DMD 1.023 (except without the string cast).

Yes, you are right.  I was looking at my tango tree, which I assumed was the 
same.  Apparently this is a Tango issue, and not a Phobos issue.

Looking at the Phobos tree, it was never this !is o, in fact it changed from 
the commented out comparison of memory addresses to the new version in 
Phobos 0.163

So I'll post this bug for Tango, and I believe now the system is at least 
correct, even if it isn't intuitive for Phobos at least.

-Steve
Next ›   Last »
1 2 3 4 5
Top | Discussion index | About this forum | D home