View mode: basic / threaded / horizontal-split · Log in · Help
April 14, 2008
Re: A fresh look at comparisons
On 14/04/2008, Scott S. McCoy <tag@cpan.org> wrote:
> I like this, except for the semantic irregularities similar to Scala
> we'd be introducing by not having the operator evaluated.
I don't understand what this means. What is Scala?


> 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. ;-)

I agree.


>  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.

I'm not sure that's a real concern. To use your example, int and long
will both implicitly convert to the same common type (in this case,
long), and the == test can be made on that.

In general, if a is of type A, and b is of type B, where A != B, and
there exists a common type C into which both A and B will implicitly
cast, then we only need to concern ourselves with (cast(C)a ==
cast(C)b).
April 14, 2008
Re: A fresh look at comparisons
On 14/04/2008, Scott S. McCoy <tag@cpan.org> wrote:
> Wouldn't that safely be 0?
>
>  If there is no order, there must be quality...assuming that all types do
>  have a natural order, even if it's just it's ordinal value. Otherwise,
>  they may not be comparable with opCmp, and maybe say, only opEquals :-)

For example, consider the complex numbers. (1+i) and (1-i) compare as
!<>=, however (1+0i) and (2+0i) compare as <, so sometimes they're
comparable and sometimes not.
April 14, 2008
Re: A fresh look at comparisons
On 14/04/2008, Janice Caron <caron800@googlemail.com> wrote:
>  In general, if a is of type A, and b is of type B, where A != B, and
>  there exists a common type C into which both A and B will implicitly
>  cast, then we only need to concern ourselves with (cast(C)a ==
>  cast(C)b).

On second thoughts...

If B and C both derive from A, and we try to compare

   B b;
   C c;
   if (b == c) ...

Should we be testing for

   if (cast(A)b == cast(A)c) ...

or should we be returning false? That's a tricky question, and I
confess I don't know the answer.

I guess I'll think about it for a while... :-)
April 14, 2008
Re: A fresh look at comparisons
Janice Caron Wrote:

> On 14/04/2008, Scott S. McCoy <tag@cpan.org> wrote:
> > I like this, except for the semantic irregularities similar to Scala
> > we'd be introducing by not having the operator evaluated.
> I don't understand what this means. What is Scala?

Scala is an open-source language that runs on the Java platform. The name is a contraction of "scalable language".

http://www.scala-lang.org/

Paul
April 14, 2008
Re: A fresh look at comparisons
Janice Caron wrote:
> [...]

There's no reason why two user-defined types that are not typically 
castable to one another can't be defined as equal... as long as this is 
transitive (so they both know about each other). Your syntax arbitrarily 
limits that option. It may be questionable software design, but it has a 
very legitimate use: backwards compatibility. For example, version 1 of 
a library could have a certain thing as a struct, but to make it more 
extensible, version 2 introduces a class version of the same type. They 
both have overridden opEquals to make them evaluate as equal to one 
another (for example, so users can use both of them in  a hash and not 
have to switch their entire codebase over at once). For efficiency, both 
the class and struct versions may live on.
April 14, 2008
Re: A fresh look at comparisons
Excellent proposal. Something along these lines is badly needed.
April 15, 2008
Re: A fresh look at comparisons
On Mon, 2008-04-14 at 19:41 +0100, Janice Caron wrote:
> On 14/04/2008, Scott S. McCoy <tag@cpan.org> wrote:
> > I like this, except for the semantic irregularities similar to Scala
> > we'd be introducing by not having the operator evaluated.
> I don't understand what this means. What is Scala?

Another programming language.

What I mean is this, you have an expression which appears just as any
other expression, but it is not evaluated as is instead "special
syntax".  This is relatively disconcerting.  In fact, you double that by
adding another operator which is often used for all-too-many other
things ("is") in your examples.


> I agree.

Yay!

> >  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.
> 
> I'm not sure that's a real concern. To use your example, int and long
> will both implicitly convert to the same common type (in this case,
> long), and the == test can be made on that.
> 
> In general, if a is of type A, and b is of type B, where A != B, and
> there exists a common type C into which both A and B will implicitly
> cast, then we only need to concern ourselves with (cast(C)a ==
> cast(C)b).

But you're making assumptions about the application's definition of
equality.  See, that's the point of having opEquals overloadable: So the
application can define equality.  And these assumptions may very well
not be safe.  Can you say definitively and for certain that in all
environments in all programs ever written in D, that an object of type A
and an object of type B will never be equal even though they have no
common ancestor?

Certainly not.

And also, we cannot say that for instance, if you have types B and C
which are both descendants of type A, that you will want B to be equal
to C if A.opEquals() returns true for the two instances.

Naturally, we could, but it would be a potentially unsafe assumption
which would reduce the possible usability of the language in some cases,
for instance when an object has a factory method to translate itself to
another type, which existed prior to it and is not an ancestor because
the target type is a part of an inheritance tree which is otherwise
irrelevant for the implementation of the source type....if that makes
any sense.  However when calling opEquals on the source type, it may
want to be equal to the instance of the target type, assuming the
planets are aligned.  It'd take me a bit of creativity to come up with a
good example, but I'm sure I could.

Cheers,
	Scott S. McCoy
April 15, 2008
Re: A fresh look at comparisons
On 15/04/2008, Scott S. McCoy <tag@cpan.org> wrote:
> But you're making assumptions about the application's definition of
> equality.

I was, and I got that part wrong, so I retract that part of the idea. See below.


>  See, that's the point of having opEquals overloadable:

I get that now. So what you're basically saying is that

   is(this == c)

isn't enough, because it makes the presumption that typeof(c) /must/
be the same as typeof(this). Whereas, we really need to allow the
programmer to overload for different types.

Still, only a minor change to my proposal would be needed to
accomodate this. We only need to allow the test to be overloaded, so:

   is(this == c) /* standard test */
   is(this == C c) /* overload for type C */


>  Can you say definitively and for certain that in all
>  environments in all programs ever written in D, that an object of type A
>  and an object of type B will never be equal even though they have no
>  common ancestor?

No, so obviously you're right, and we would need to make it
overloadable, as per the above example.


>  And also, we cannot say that for instance, if you have types B and C
>  which are both descendants of type A, that you will want B to be equal
>  to C if A.opEquals() returns true for the two instances.

You're right, and that's one thing I don't like about opEquals, and
which is(==) would solve. So, under the revised proposal:

   class A
   {
       is(this == c) {...}
   }

   class B : A {}
   class C : A {}

   B b;
   C c;

   if (b == c) ...

This test would now have to return false always, because the new rules
must compare B with B, or C with C, and must /not/ cast b and c to
their common type. Under the new rules, to compare B with C, the
programmer would now have to write

   class B : A
   {
       is(this == C c) {...}
   }

   class C : A
   {
       is(this == B b) {...}
   }

I think that covers all the edge cases that people have pointed out.
With that adjustment, the scheme, I think, now works a treat.

As you know, in existing D, opEquals(), at least for classes, is
defined to take an Object parameter, not a C parameter, so even
ignoring the inheritance rules, allowing opEquals to compare against
multiple types is tricky. You end up writing code like

   int opEquals(Object o)
   {
       A a = cast(A)o;
       B b = cast(B)o;
       C c = cast(C)o;

       if (c !is null)
       {
           ...
       }
       else if (b !is null)
       {
           ...
       }
       else if (a !is null)
       {
           ...
       }
   }

which is not ideal
April 15, 2008
Re: A fresh look at comparisons
On Tue, 15 Apr 2008 07:49:21 +0100, Janice Caron <caron800@googlemail.com>  
wrote:

> On 15/04/2008, Scott S. McCoy <tag@cpan.org> wrote:
>
>>  Can you say definitively and for certain that in all
>>  environments in all programs ever written in D, that an object of type  
>> A
>>  and an object of type B will never be equal even though they have no
>>  common ancestor?
>
> No, so obviously you're right, and we would need to make it
> overloadable, as per the above example.
>
>
>>  And also, we cannot say that for instance, if you have types B and C
>>  which are both descendants of type A, that you will want B to be equal
>>  to C if A.opEquals() returns true for the two instances.
>
> You're right, and that's one thing I don't like about opEquals, and
> which is(==) would solve. So, under the revised proposal:
>
>     class A
>     {
>         is(this == c) {...}
>     }
>
>     class B : A {}
>     class C : A {}
>
>     B b;
>     C c;
>
>     if (b == c) ...
>
> This test would now have to return false always, because the new rules
> must compare B with B, or C with C, and must /not/ cast b and c to
> their common type.

I don't think even this is safe. The normal definition of inheritance is  
that
you can use B or C wherever you could use an A. Its probably safe that it  
doesn't
compile by default.

> Under the new rules, to compare B with C, the
> programmer would now have to write
>
>     class B : A
>     {
>         is(this == C c) {...}
>     }
>
>     class C : A
>     {
>         is(this == B b) {...}
>     }
>
> I think that covers all the edge cases that people have pointed out.
> With that adjustment, the scheme, I think, now works a treat.
>
Yes. The semantics are there. We just need to find a syntax that fits  
better with D.
This doesn't quite sit right when everything else is an Op-something.

Regards,

Bruce.
April 15, 2008
Re: A fresh look at comparisons
On 15/04/2008, Bruce Adams <tortoise_74@yeah.who.co.uk> wrote:
>  I don't think even this is safe. The normal definition of inheritance is

Well, that's kindof the point. is(==) wouldn't /use/ inheritance. It
wouldn't be inheritable. It's just like constructors aren't
inheritable.

The whole point is the recognition that inheritance is not the right
mechanism when it comes to comparisons, and hence the quest for a
different mechanism that /is/ appropriate.


>  Yes. The semantics are there. We just need to find a syntax that fits
> better with D.
>  This doesn't quite sit right when everything else is an Op-something.

It wouldn't be a function in the conventional sense. Choosing a syntax
that makes it look /not/ like a function is entirely deliberate. It's
just like constructors don't look like functions; destructors don't
look like functions; class invariants don't look like functions; unit
tests don't look like functions.

opAdd() is a function, subject to all the normal rules of functions,
including inheritance, overloading, overriding, etc. They're different
beasts, so they need to look different, syntactically.

(Also, I chose "is" so as to avoid introducing a new keyword).
1 2 3 4 5
Top | Discussion index | About this forum | D home