This page is no longer maintained — Please continue to the home page at www.scala-lang.org

equality questions

21 replies
extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.

I hope this document doesn't seem like overkill, but it's
about the only way I can come up with to get everyone on the
same page. So I apologize for the imposition of asking
how about 20 expressions should evaluate.

*** The "understood" pieces ***

Here are the bits that are (as far as I understand) clear
according to the current proposal.

* x.hashCode is what it is, we can't touch it.
* x.equals(y) also is what it is, but we don't feel obliged to
return the same result with == as we do with equals.
* if x.equals(y) then we require that x.hashCode == y.hashCode.
* comparing two values that are statically typed as primitives
with == will behave the same as in java. Some examples:

5 == (5: Byte)
5 == (5: Short)
5 == 5
5L == 5
5.0d == 5.0f == 5
65 == 'A'

* comparing two values that are statically known to be the same
class of boxed primitives with == will also be like java, examples:

(new jl.Long(5) == new jl.Long(5))
(new jl.Short(5) == new jl.Short(5))

*** Below this point are all questions I am asking ***

In most cases I might know what's implied by the overloading
proposal, but I've struggled with this for so long, there's not going
to be any such thing as "too clear". And I also wanted to discover
if clarifying all these would lead to reconsidering the idea.

(new jl.Long(5) == new jl.Float(5))
(new jl.Long(5) == (new jl.Float(5): Number))
(new jl.Long(5) == (new jl.Float(5): AnyRef))
(new jl.Long(5) == (new jl.Float(5): Any))

5 == (5L: Any)
5 == (5L: AnyVal)
5 == (5L: Any with Long)
5 == (5L: Long with Any)
5 == (5L: Any with AnyVal with Long with AnyVal with Any)

List(5,10,15) contains 5
List(5,10,15) contains 5L
List(5,10,15) find (_ == 5L)

List[Any](5,10,15) contains 5
List[Any](5,10,15) contains 5L
List[Any](5,10,15) find (_ == 5L)

List(5,10,15,20.0) contains 5
List(5,10,15,20.0) contains 5L
List(5,10,15,20.0) find (_ == 5L)

Map[Int,String](5 -> "abc") ++ Map[Float,String](5.0f -> "def")

5 match { case 5L => true ; case _ => false }
5.0 match { case 5L => true ; case _ => false }
(5: Any) match { case 5L => true ; case _ => false }

// some support code for the last few
def cmp(x1: AnyVal, x2: AnyVal) = x1 == x2

case class Foo[+T <: AnyVal](x: T)
case class Constant(x: Any)

def f1(c: Constant) = c match { case Constant(5) => true ; case _ => false }
def f2(c: Constant) = c match { case Constant(5: Int) => true ; case _ => false }

// and the last few questions
cmp(5, 5L)

Foo(5L) == Foo(5.0f)
Foo[scala.Long](5L).x == Foo[scala.Float](5.0f).x
Foo(5L).hashCode == Foo(5.0f).hashCode

f1(Constant(5.0f))
f2(Constant(5.0f))

...and just for extra fun, how about if Foo is @specialized.

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: equality questions

On Thu, Sep 17, 2009 at 9:20 PM, Paul Phillips wrote:
> I hope this document doesn't seem like overkill, but it's
> about the only way I can come up with to get everyone on the
> same page.  So I apologize for the imposition of asking
> how about 20 expressions should evaluate.
>
> *** The "understood" pieces ***
>
> Here are the bits that are (as far as I understand) clear
> according to the current proposal.
>
>  * x.hashCode is what it is, we can't touch it.
>  * x.equals(y) also is what it is, but we don't feel obliged to
>     return the same result with == as we do with equals.
>  * if x.equals(y) then we require that x.hashCode == y.hashCode.
>  * comparing two values that are statically typed as primitives
>     with == will behave the same as in java.  Some examples:
>
>      5 == (5: Byte)
>      5 == (5: Short)
>      5 == 5
>      5L == 5
>      5.0d == 5.0f == 5
>      65 == 'A'
>
>  * comparing two values that are statically known to be the same
>   class of boxed primitives with == will also be like java, examples:
>
>      (new jl.Long(5) == new jl.Long(5))
>      (new jl.Short(5) == new jl.Short(5))
>
> *** Below this point are all questions I am asking ***
>
> In most cases I might know what's implied by the overloading
> proposal, but I've struggled with this for so long, there's not going
> to be any such thing as "too clear".  And I also wanted to discover
> if clarifying all these would lead to reconsidering the idea.
>
>
> (new jl.Long(5) == new jl.Float(5))

same as Java, i.e no

> (new jl.Long(5) == (new jl.Float(5): Number))

no again

> (new jl.Long(5) == (new jl.Float(5): AnyRef))

no again

> (new jl.Long(5) == (new jl.Float(5): Any))

no again
>
> 5 == (5L: Any)

no

> 5 == (5L: AnyVal)

no

> 5 == (5L: Any with Long)

yes

> 5 == (5L: Long with Any)

yes

> 5 == (5L: Any with AnyVal with Long with AnyVal with Any)
>
yes (the last three are really the same; all three types are equivalent to Long)

> List(5,10,15) contains 5

yes

> List(5,10,15) contains 5L

no

> List(5,10,15) find (_ == 5L)
>

yes

> List[Any](5,10,15) contains 5

yes

> List[Any](5,10,15) contains 5L

no

> List[Any](5,10,15) find (_ == 5L)
>
no

> List(5,10,15,20.0) contains 5

no (I assume they are all converted to floats)

> List(5,10,15,20.0) contains 5L

no

> List(5,10,15,20.0) find (_ == 5L)
>
this one's interesting. It's a List[AnyVal]. I would assume no then
because there
would be no overloaded instance of == on AnyVal.

> Map[Int,String](5 -> "abc") ++ Map[Float,String](5.0f -> "def")
>
That should give a map with two entries.

>       5 match { case 5L => true ; case _ => false }

Not sure. Should pattern matching use overloaded == or equals?

>     5.0 match { case 5L => true ; case _ => false }

see last one.

> (5: Any) match { case 5L => true ; case _ => false }
>
certainly false

> // some support code for the last few
> def cmp(x1: AnyVal, x2: AnyVal) = x1 == x2
>
> case class Foo[+T <: AnyVal](x: T)
> case class Constant(x: Any)
>
> def f1(c: Constant) = c match { case Constant(5) => true ; case _ => false }
> def f2(c: Constant) = c match { case Constant(5: Int) => true ; case _ => false }
>
> // and the last few questions
> cmp(5, 5L)
>
false

> Foo(5L) == Foo(5.0f)

false

> Foo[scala.Long](5L).x == Foo[scala.Float](5.0f).x

false

> Foo(5L).hashCode == Foo(5.0f).hashCode
>
false

> f1(Constant(5.0f))
> f2(Constant(5.0f))
>
false and false
>
> ...and just for extra fun, how about if Foo is @specialized.
>
good question! Specialization should not change the meaning. Iulian
should have a look at this.

I agree that all this makes a mockery of equality laws if == is
naively taken to be equality.
The only two alternatives I see are:

(1) Disallow == between different numeric types. This would break a
HUGE amount of code.
(2) Silently change the behavior of comparisons so that new Scala is
different from both old Scala and Java. I fear that's unacceptable.

So, is there another alternative left? It is galling what Java
interoperability forces us to do, but right now I see no other choice.

Cheers

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: equality questions

On Fri, Sep 18, 2009 at 07:53:59PM +0200, martin odersky wrote:
> > List(5,10,15,20.0) contains 5
>
> no (I assume they are all converted to floats)

No, it infers List[AnyVal]. So that one's true.

> > Map[Int,String](5 -> "abc") ++ Map[Float,String](5.0f -> "def")
> >
> That should give a map with two entries.

Noting for the record that this is a silent behavioral change (right now
that's one map.)

> >       5 match { case 5L => true ; case _ => false }
>
> Not sure. Should pattern matching use overloaded == or equals?

As a general principle, if you specify the type in a pattern match, then
it should only match if the scrutinee is of that type. So to me "case
5L" should be like saying "case 5: Long" if only you could say that, but
you can't because the matcher syntax doesn't allow for mixing literals
and types, it's one or the other. IOW you can't say

x match {
case 5: Short => ...
case 5: Byte => ...
}

but should you be able to? It appears strictly more general.

So I would say "5 match { case 5L => true ; case _ => false }" should be
false: not because it uses ==, but because the type doesn't match.

However if no type is given, then I would expect it to use ==. So:

(5: Short) match { case 5 => true }

would be true, but

(5: Short) match { case 5: Int => true }

would not.

If that seems too complicated -- and it does -- I'm all for something
less complicated, but I fear the only way to reduce complication is to
increase surprise.

>     5.0 match { case 5L => true ; case _ => false }

So according to the above, this would be false, but { case 5 => true }
would be true.

> > Foo(5L) == Foo(5.0f)
>
> false

I have trouble swallowing this one. This would also mean that:

(x1 == y1) && (x2 == y2) implies (x1, x2) == (y1, y2)

is no longer true. (I think it's true now, anyway.)

> So, is there another alternative left? It is galling what Java
> interoperability forces us to do, but right now I see no other choice.

I am forced to continue looking, because to me this cure looks worse
than the disease. I think I would much rather continue to violate the
equals/hashcode contract and maybe simply loudly document that, than I
would to start dealing with this kind of overloading behavior. Also
possibly more convincing to you would be the full list of changes I have
to make to the compiler and test cases after changing == to behave this
way; but I don't have that whole list because I haven't done it yet (I
have it down to 8 failing tests at the moment.)

milessabin
Joined: 2008-08-11,
User offline. Last seen 33 weeks 3 days ago.
Re: equality questions

On Fri, Sep 18, 2009 at 10:53 AM, martin odersky wrote:
> (1) Disallow == between different numeric types. This would break a
> HUGE amount of code.

I think this is the only sane solution and we should just bite the
bullet and do it.

Yes, it will break a lot of code, but the fixes will be immediately
obvious, and it's quite possible (likely even) that code that depended
on equality between different numeric types was subtly broken anyway.

Cheers,

Miles

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: equality questions

On Fri, Sep 18, 2009 at 8:52 PM, Paul Phillips wrote:
> On Fri, Sep 18, 2009 at 07:53:59PM +0200, martin odersky wrote:
>> > List(5,10,15,20.0) contains 5
>>
>> no (I assume they are all converted to floats)
>
> No, it infers List[AnyVal].  So that one's true.
>
>> > Map[Int,String](5 -> "abc") ++ Map[Float,String](5.0f -> "def")
>> >
>> That should give a map with two entries.
>
> Noting for the record that this is a silent behavioral change (right now
> that's one map.)
>
I see. Does hat also hold for java.util.Maps, however? I believe up to
now most people were using those.

>> >       5 match { case 5L => true ; case _ => false }
>>
>> Not sure. Should pattern matching use overloaded == or equals?
>
> As a general principle, if you specify the type in a pattern match, then
> it should only match if the scrutinee is of that type.

I disagree with that. I think that should only hold when you bind
pattern variables
(there was a separate discussion about this, I believe). That is:

x @ P should be translated to x @ P if x.isInstanceOf[]

>  So to me "case
> 5L" should be like saying "case 5: Long" if only you could say that, but
> you can't because the matcher syntax doesn't allow for mixing literals
> and types, it's one or the other.  IOW you can't say
>
>  x match {
>    case 5: Short => ...
>    case 5: Byte => ...
>  }
>
> but should you be able to? It appears strictly more general.
>
We might generalize this, yes. It's not high priority but would be
nice. But in any case I disagree that case 5L should then be the same
as case 5: Long, and therefore I also disagree with this:

> So I would say "5 match { case 5L => true ; case _ => false }" should be
> false: not because it uses ==, but because the type doesn't match.
>

> However if no type is given, then I would expect it to use ==.  So:
>
>  (5: Short) match { case 5 => true }
>
> would be true, but
>
OK.

>  (5: Short) match { case 5: Int => true }
>
> would not.
>
OK.

>>     5.0 match { case 5L => true ; case _ => false }
>
> So according to the above, this would be false,

I'd get true because no type is given.

> but { case 5 => true }
> would be true.
>
OK.

>> > Foo(5L) == Foo(5.0f)
>>
>> false
>
> I have trouble swallowing this one.  This would also mean that:
>
>  (x1 == y1) && (x2 == y2)  implies  (x1, x2) == (y1, y2)
>
> is no longer true.  (I think it's true now, anyway.)
>
Yes, == equality stops being compositional. It's not compositional in
Java either, and that
unfortunately pollutes what we can do in Scala.

>> So, is there another alternative left? It is galling what Java
>> interoperability forces us to do, but right now I see no other choice.
>
> I am forced to continue looking, because to me this cure looks worse
> than the disease.  I think I would much rather continue to violate the
> equals/hashcode contract and maybe simply loudly document that, than I
> would to start dealing with this kind of overloading behavior.  Also
> possibly more convincing to you would be the full list of changes I have
> to make to the compiler and test cases after changing == to behave this
> way; but I don't have that whole list because I haven't done it yet (I
> have it down to 8 failing tests at the moment.)
>
In fact, I believe the list would be not that extensive (maybe I'm
overlooking things):
1) Rip out a lot of the special run-time treatment that implements
equals. (I have a feeling this could help performance in some cases).
2) remove final modifier for Any.==
2) Define some overloaded methods for == on numeric and collection classes. E.g.

class Int {
def ==(other: Double) = ...
def ==(other: Float) = ...
def ==(other: Long) =
def ==(other: Int) = ...
}

I'm still thinking about it but so far I find it very scary to break
the equals/hashCode contract. The only halfway sane way out would need
multi-methods. If == were a multi-method instead of being overloaded
statically, we could arrange so that

(x1 == y1) && (x2 == y2) implies (x1, x2) == (y1, y2)

But that's not in the cards for now. I believe to stay within the
complexity budget of Scala we can do multi-methods only if we replace
static overloading with them, and we's need to do this seamlessly,
without breaking code. That's a difficult design, and a lot of
implementation work and all that with considerable risk because only
at the end we'd find out whether we can do this without breaking some
important programs inheriting from the many overloaded functions in
frameworks like Swing. So it all might come to nothing in the end.

Cheers

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: equality questions

There's a way to look at equality which is really rather simple, both
to specify and to implement:

First, let's remove all the cruft that's in our ScalaRunTime.equals
method. It slows down programs and it's leaky anyway, because from
Java things are seen differently. Let's simply come back to what is in
the SLS: In class Any, there's an equality method specified as follows

final def == (that: Any): Boolean =
if (null eq this) null eq that else this equals that

And this equality method is inherited by the `null' object.

Let's not touch equals and hashCode. They are what they are in Java.

Let case classes re-implement equals, as usual.

If we stop here, we get the model which differs from Java in that (0:
Short) != 0 != 0L != 0.0. As I have argued before, I think that's
unacceptable. To make up for this, let's introduce a set of overloaded
== methods in the synthetic primitive number classes.. E.g.

class Int {
def ==(other: Int) =
def ==(other: Double) = other == this.toDouble
def ==(other: Float) = other = this.toFloat
...
}

class Double {
def ==(other: Double) =
}

class Byte {
def ==(other: Int =
(rest as in Int)
}

I think that's all. Nothing else needed. Amazingly, this is to the
letter what's specified in the SLS! The Scala implementation never
followed the spec, because the overloaded equality methods in the
primitive classes were missing. This led to problems where we had to
equate boxed and unboxed versions of numbers, say, and this led to all
the cruft in ScalaRunTime.equals. I believe if we had just followed
the spec, we'd never have had these troubles.

Admittedly, the waters were muddied so far because of the problems
with string.reverse.reverse, with equating arrays and boxed arrays,
and so on. This led us to believe that aequality needed to become more
complicated ather than simpler. Fortunately, all these problems are
now solved with the new treatment of arrays and strings.

So, let's try this. I am very hopefull it will work out OK.

There's one question still to be answered: Should case classes also
implement an overloaded == method, or should they restrict themselves
to implementing`equals', as is the case now? My tendency would be to
say, stick to equals, because overloading == leads to complicated
behavior, so the less we use it the better.

Cheers

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: equality questions

On Sat, Sep 19, 2009 at 6:38 AM, Miles Sabin wrote:
> On Fri, Sep 18, 2009 at 10:53 AM, martin odersky wrote:
>> (1) Disallow == between different numeric types. This would break a
>> HUGE amount of code.
>
> I think this is the only sane solution and we should just bite the
> bullet and do it.

It's not so simple. If we do this we could not have universal equality
anymore. This would change the game for almost every library out
there. I believe it's an option if Scala came fresh from the drawing
board today (I am not sure it's the best option, though). But we an
existing code base out there we can't do this.

Cheers

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: equality questions

Sorry I missed asking about this the first time around:

On Fri, Sep 18, 2009 at 07:53:59PM +0200, martin odersky wrote:
> > case class Foo[+T <: AnyVal](x: T)
>
> > Foo[scala.Long](5L).x == Foo[scala.Float](5.0f).x
>
> false

Are you sure you meant false here? Is this not equivalent to

(5L: Long) == (5.0f: Float)

which is true? If it is not equivalent, can you clarify what is the
distinction?

If this is true (which I thought it should be) it means that in general
two case classes can have each argument equal but still be judged unequal.

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: equality questions

So now I have logging informing me of all kinds of interesting things
about comparisons. With respect to my earlier question:

> > > case class Foo[+T <: AnyVal](x: T)
> >
> > > Foo[scala.Long](5L).x == Foo[scala.Float](5.0f).x
> >
> > false
>
> Are you sure you meant false here?

Here is what happens if we log how scala thinks:

scala> case class Foo[+T <: AnyVal](x: T)
defined class Foo

scala> Foo(5) == Foo(5L)
// Changed == answer for: 5 (Long) == 5 (Integer)
res0: Boolean = true

scala> Foo(5).x == Foo(5L).x
// [ KNOWN] Int == Long
res1: Boolean = true

What does that mean? It means when you compare Foo(5) and Foo(5L) it
compares the boxed versions. At present scala intercepts these and
gives a different answer than java would. But if you compare Foo(5).x
and Foo(5L).x it compares the primitives with the known static types of
Int and Long.

Thus under the proposed overloading:

Foo(5) != Foo(5L)
Foo(5).x == Foo(5L).x

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: equality questions

On Tue, Sep 22, 2009 at 1:58 AM, Paul Phillips wrote:
> So now I have logging informing me of all kinds of interesting things
> about comparisons.  With respect to my earlier question:
>
>> > > case class Foo[+T <: AnyVal](x: T)
>> >
>> > > Foo[scala.Long](5L).x == Foo[scala.Float](5.0f).x
>> >
>> > false
>>
>> Are you sure you meant false here?
>
> Here is what happens if we log how scala thinks:
>
> scala> case class Foo[+T <: AnyVal](x: T)
> defined class Foo
>
> scala> Foo(5) == Foo(5L)
> // Changed == answer for: 5 (Long) == 5 (Integer)
> res0: Boolean = true
>
> scala> Foo(5).x == Foo(5L).x
> // [  KNOWN] Int == Long
> res1: Boolean = true
>
> What does that mean? It means when you compare Foo(5) and Foo(5L) it
> compares the boxed versions.  At present scala intercepts these and
> gives a different answer than java would.  But if you compare Foo(5).x
> and Foo(5L).x it compares the primitives with the known static types of
> Int and Long.
>
Yes. In fact, following my proposition, only equals would
be automatically provided for case classes. So Foo(5) and Foo(5L)
would always be different wrt both == and equals.

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: equality questions

I don't think assembling a bunch of cases will decide this issue. In
the end you can always choose your examples to suit, and casual
readers will always prefer the largest possible meaning of equality,
because the cases typically do not show where one then falls off a
cliff.

What should decide the issue it is the clarity and simplicity of spec
language. That's the ultimate test, IMO.

Now, let's disect this issue rationally.

To me there are two non-negotiables.:

1) We cannot mess with the equals/hashCode contract.
2) == in Scala must yield true wherever == in Java yields true.

If you stronly disgagree with either one, try to convince me
otherwise, but it will be difficult.
To me, breaking 1) is too scary to contemplate. As to 2), one of the
design principles of Scala is that expressions should use the usual
syntax and meaning of Java, so breaking this is very problematic.
Besides, you'd run into other consistency issues such as:

5 == 5L disallowed, but
5 <= 5L && 5L <= 5 allowed.

So these two are non-negotiable. What can we do under these
constraints? The most conservative
scheme, which is the one I propose, is to have two notions of
equality: The first is the same as equals, except that it deals
correctly with nulls. The second is the same as numeric == in Java.
Now for utmost clarity, the two equalities could be named differently,
e.g == and num_==. If we did that, then all paradoxical examples would
go away. But for compatibilty and convenience we don't do that and use
instead the same name for the two kinds of equalility by means of
static overloading. You just have to keep in mind that there are two
different notions of equality and you are fine. (and btw dealing with
static overloading is what Java and Scala programmers do every day).

Are there alternatives? The most obvious generalization is to make ==
a multi-method.
That's similar to the current scheme, except that we should avoid any
leakage into equals. We have to make sure that equals only ever calls
equals, never ==. But having == be a multi-method by itself seems not
very useful, because you still would get Foo(5) != Foo(5L). Besides,
it introduces a concept that's foreign to Scala so far, so is bound to
increase the spec footprint considerably.

To go further, we could also consider making == into a congruence, as
far as is feasable. This means that case classes would need to
redefine (multi-method cases of) ==, next to what they already do with
equals.
And user-defined classes would have to do the same of course. Given
that the necessary double dispatch is far from easy to implement, this
seems like a huge complication of user code, for not much gain.

So in the end, I think the conservative scheme with two equalities wins out.

Cheers

alblue
Joined: 2009-09-02,
User offline. Last seen 3 years 7 weeks ago.
Re: equality questions

On 23 Sep 2009, at 09:23, martin odersky wrote:

> I don't think assembling a bunch of cases will decide this issue. In
> the end you can always choose your examples to suit, and casual
> readers will always prefer the largest possible meaning of equality,
> because the cases typically do not show where one then falls off a
> cliff.
>
> What should decide the issue it is the clarity and simplicity of spec
> language. That's the ultimate test, IMO.
>
> Now, let's disect this issue rationally.
>
> To me there are two non-negotiables.:
>
> 1) We cannot mess with the equals/hashCode contract.
> 2) == in Scala must yield true wherever == in Java yields true.

That sounds like a solid design idea to me. However, note that Java's
autoboxing already causes problems so it seems to me that Scala is no
worse than that.

import java.util.*;
public class Test {
public static void main(String args[]) {
int i = 5;
float f = 5.0f;
System.out.println( "i==f " + (i == f ));
List il = new ArrayList();
il.add(i);
List fl = new ArrayList();
fl.add(f);
System.out.println( "il==fl " + (il.equals(fl)));
}
}

alblue:tmp alex$ java Test
i==f true
il==fl false

Replace 'list' with 'Foo' in the case class example, and you're back
to the same starting point as before.

Alex

Kevin Wright
Joined: 2009-06-09,
User offline. Last seen 49 weeks 3 days ago.
Re: equality questions

 5 == 5L  disallowed, but
 5 <= 5L && 5L <= 5 allowed.

That actually looks pretty valid to me!
When you consider that a number consists of both value and precision, then the first statement is testing both qualities, but the second statement is comparing only the values.

I did imagine a scheme where equality and hashCode act as though the numbers on both sides of the test had been converted to the highest possible precision
but given the rounding errors that can occur in converting from floats to decimals, this could fail badly when BigInts/BigDecimals are involved
 
milessabin
Joined: 2008-08-11,
User offline. Last seen 33 weeks 3 days ago.
Re: equality questions

On Wed, Sep 23, 2009 at 9:23 AM, martin odersky wrote:
> To me there are two non-negotiables.:
>
> 1) We cannot mess with the equals/hashCode contract.

Agreed.

> 2) == in Scala must yield true wherever == in Java yields true.

2) as given is clearly false now (and has been for as long as I can
remember), so I assume what you really meant was,

2) == in Scala for values of primitive types must yield true whenever
== in Java for values of primitive types yields true.

I'm just not convinced that this should be treated as non-negotiable.
We've already changed the intuitive semantics of == for reference
types, so why shouldn't we do the same for primitive types if there's
a strong reason for doing so: and if our current troubles with
equality aren't a strong enough reason then I don't know what would
be.

On the contrary, I think the burden of proof runs the other way.
Equalities between numeric values of different primitive types is a
notorious source of subtle bugs in numerous languages and we'd be
justified in changing the semantics independently of the current
issue.

I'm also still waiting for a realistic example of how changing things
here would harm Java interop ...

Cheers,

Miles

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: equality questions

On Wed, Sep 23, 2009 at 4:22 PM, Miles Sabin wrote:
> On Wed, Sep 23, 2009 at 9:23 AM, martin odersky wrote:
>> To me there are two non-negotiables.:
>>
>> 1) We cannot mess with the equals/hashCode contract.
>
> Agreed.
>
>> 2) == in Scala must yield true wherever == in Java yields true.
>
> 2) as given is clearly false now (and has been for as long as I can
> remember), so I assume what you really meant was,
>
Can you elaborate? Do you mean you can write an equals method where
!(x equals x)?
Sure, but I do not consider that case very interesting.

> 2) == in Scala for values of primitive types must yield true whenever
> == in Java for values of primitive types yields true.
>
> I'm just not convinced that this should be treated as non-negotiable.
> We've already changed the intuitive semantics of == for reference
> types, so why shouldn't we do the same for primitive types if there's
> a strong reason for doing so: and if our current troubles with
> equality aren't a strong enough reason then I don't know what would
> be.
>
Because it would go the other way. Scala equates more reference types
than Java, and this is a considered a good thing by most people. It
would be something else entirely to now start to equate fewer
primitive types.

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: equality questions

On Wed, Sep 23, 2009 at 10:23:08AM +0200, martin odersky wrote:
> I don't think assembling a bunch of cases will decide this issue. In
> the end you can always choose your examples to suit, and casual
> readers will always prefer the largest possible meaning of equality,
> because the cases typically do not show where one then falls off a
> cliff.

I'm trying for the cliff cases, not the examples to suit, and I'm not
trying to persuade the casual reader so much as I'm trying to identify
an example which will seem to you to be beyond the pale.

> What should decide the issue it is the clarity and simplicity of spec
> language. That's the ultimate test, IMO.

To me this sounds like "we had to destroy the village to save it" in the
sense that the cart is driving the horse. Reducing to the absurd, "==
is always true" is the clearest and simplest spec language I can devise
for us. The spec is a means to an end. I'm focused on the end: does
this make scala more or less of a langauge I want to use?

I know we both wish I'd just agree and do it, but my little voice simply
will not stop pestering me. He says, "Noooooo!"

I don't know if a reference to Alan Smithee will be understood with our
international audience, so:

http://en.wikipedia.org/wiki/Alan_Smithee

My hope it to avoid "Equality: An Alan Smithee Production." I know we
can find a better way. As long as I'm tasked with this I cannot give up
hope on finding it until all avenues are exhausted.

Matt Fowles
Joined: 2009-07-09,
User offline. Last seen 42 years 45 weeks ago.
Re: equality questions
All~

Have we considered the option of using exactly java's equality operator with absolutely no extra niceties and then define a smart equals operator for scala '=:=' that isn't burdened by compatibility?

This would provide full java compat (definitionally) while giving people who want it something useful to use in scala code...

Matt

On Wed, Sep 23, 2009 at 10:42 AM, Paul Phillips <paulp@improving.org> wrote:
On Wed, Sep 23, 2009 at 10:23:08AM +0200, martin odersky wrote:
> I don't think assembling a bunch of cases will decide this issue. In
> the end you can always choose your examples to suit, and casual
> readers will always prefer the largest possible meaning of equality,
> because the cases typically do not show where one then falls off a
> cliff.

I'm trying for the cliff cases, not the examples to suit, and I'm not
trying to persuade the casual reader so much as I'm trying to identify
an example which will seem to you to be beyond the pale.

> What should decide the issue it is the clarity and simplicity of spec
> language. That's the ultimate test, IMO.

To me this sounds like "we had to destroy the village to save it" in the
sense that the cart is driving the horse.  Reducing to the absurd, "==
is always true" is the clearest and simplest spec language I can devise
for us.  The spec is a means to an end.  I'm focused on the end: does
this make scala more or less of a langauge I want to use?

I know we both wish I'd just agree and do it, but my little voice simply
will not stop pestering me.  He says, "Noooooo!"

I don't know if a reference to Alan Smithee will be understood with our
international audience, so:

 http://en.wikipedia.org/wiki/Alan_Smithee

My hope it to avoid "Equality: An Alan Smithee Production." I know we
can find a better way.  As long as I'm tasked with this I cannot give up
hope on finding it until all avenues are exhausted.

--
Paul Phillips      | Giving every man a vote has no more made men wise
Everyman           | and free than Christianity has made them good.
Empiricist         |     -- H. L. Mencken
slap pi uphill!    |----------* http://www.improving.org/paulp/ *----------

milessabin
Joined: 2008-08-11,
User offline. Last seen 33 weeks 3 days ago.
Re: equality questions

On Wed, Sep 23, 2009 at 3:32 PM, martin odersky wrote:
> On Wed, Sep 23, 2009 at 4:22 PM, Miles Sabin wrote:
>> On Wed, Sep 23, 2009 at 9:23 AM, martin odersky wrote:
>>> To me there are two non-negotiables.:
>>>
>>> 1) We cannot mess with the equals/hashCode contract.
>>
>> Agreed.
>>
>>> 2) == in Scala must yield true wherever == in Java yields true.
>>
>> 2) as given is clearly false now (and has been for as long as I can
>> remember), so I assume what you really meant was,
>>
> Can you elaborate? Do you mean you can write an equals method where
> !(x equals x)?
> Sure, but I do not consider that case very interesting.

No, but you didn't mention "equals": you referred to "==" in your original.

>> I'm just not convinced that this should be treated as non-negotiable.
>> We've already changed the intuitive semantics of == for reference
>> types, so why shouldn't we do the same for primitive types if there's
>> a strong reason for doing so: and if our current troubles with
>> equality aren't a strong enough reason then I don't know what would
>> be.
>>
> Because it would go the other way. Scala equates more reference types
> than Java, and this is a considered a good thing by most people. It
> would be something else entirely to now start to equate fewer
> primitive types.

Right, but this is just repeating the observation that it's different.
And in this case it's not at all clear that people consider equating
values of different primitive types as beneficial: on the contrary, as
I keep repeating, it's a common source of subtle bugs.

Other than that, are there any concrete issues here? Can you show me
an example of a piece of code under the current scheme which would be
broken by the change and the correct fix would be non-obvious? As
Pauls run through the scalac codebase yesterday suggests, the main
breakage will be examples of the form,

val l = 23L
// ...
if (l == 0) ...
^
Proposed error: equality between Long and Int

but these are trivially fixable, and the simplest fix that shuts the
compiler up is also the semantically correct one.

And I've really tried to come up with an example where Java interop
would be damaged and I can't ...

Cheers,

Miles

extempore
Joined: 2008-12-17,
User offline. Last seen 35 weeks 3 days ago.
Re: equality questions

On Wed, Sep 23, 2009 at 10:47:23AM -0400, Matt Fowles wrote:
> Have we considered the option of using exactly java's equality
> operator with absolutely no extra niceties and then define a smart
> equals operator for scala '=:=' that isn't burdened by compatibility?

It's what I've wanted since the word go, but martin says no new equality
operators.

Matt Fowles
Joined: 2009-07-09,
User offline. Last seen 42 years 45 weeks ago.
Re: equality questions
All~

Can anyone provide a pointer to or summary of the reason(s) why we cannot add a new equality operator?

I think it is clear that Java interop produces a fundamentally incoherent result, so why not push the issue aside?  If it is merely a question of "the common thing should do the intuitive thing", then make '==' smart equals and make '=:=' (or '===' or whatever) java equals.

Matt

On Wed, Sep 23, 2009 at 10:49 AM, Paul Phillips <paulp@improving.org> wrote:
On Wed, Sep 23, 2009 at 10:47:23AM -0400, Matt Fowles wrote:
> Have we considered the option of using exactly java's equality
> operator with absolutely no extra niceties and then define a smart
> equals operator for scala '=:=' that isn't burdened by compatibility?

It's what I've wanted since the word go, but martin says no new equality
operators.

--
Paul Phillips      | Appreciation is a wonderful thing; it makes what is
Apatheist          | excellent in others belong to us as well.
Empiricist         |     -- Voltaire
pull his pi pal!   |----------* http://www.improving.org/paulp/ *----------

odersky
Joined: 2008-07-29,
User offline. Last seen 45 weeks 6 days ago.
Re: equality questions

On Wed, Sep 23, 2009 at 4:50 PM, Miles Sabin wrote:
> On Wed, Sep 23, 2009 at 3:32 PM, martin odersky wrote:
>> On Wed, Sep 23, 2009 at 4:22 PM, Miles Sabin wrote:
>>> On Wed, Sep 23, 2009 at 9:23 AM, martin odersky wrote:
>>>> To me there are two non-negotiables.:
>>>>
>>>> 1) We cannot mess with the equals/hashCode contract.
>>>
>>> Agreed.
>>>
>>>> 2) == in Scala must yield true wherever == in Java yields true.
>>>
>>> 2) as given is clearly false now (and has been for as long as I can
>>> remember), so I assume what you really meant was,
>>>
>> Can you elaborate? Do you mean you can write an equals method where
>> !(x equals x)?
>> Sure, but I do not consider that case very interesting.
>
> No, but you didn't mention "equals": you referred to "==" in your original.
>
>>> I'm just not convinced that this should be treated as non-negotiable.
>>> We've already changed the intuitive semantics of == for reference
>>> types, so why shouldn't we do the same for primitive types if there's
>>> a strong reason for doing so: and if our current troubles with
>>> equality aren't a strong enough reason then I don't know what would
>>> be.
>>>
>> Because it would go the other way. Scala equates more reference types
>> than Java, and this is a considered a good thing by most people. It
>> would be something else entirely to now start to equate fewer
>> primitive types.
>
> Right, but this is just repeating the observation that it's different.
> And in this case it's not at all clear that people consider equating
> values of different primitive types as beneficial: on the contrary, as
> I keep repeating, it's a common source of subtle bugs.
>
> Other than that, are there any concrete issues here? Can you show me
> an example of a piece of code under the current scheme which would be
> broken by the change and the correct fix would be non-obvious? As
> Pauls run through the scalac codebase yesterday suggests, the main
> breakage will be examples of the form,
>
>  val l = 23L
>  // ...
>  if (l == 0) ...
>           ^
>  Proposed error: equality between Long and Int
>
> but these are trivially fixable, and the simplest fix that shuts the
> compiler up is also the semantically correct one.

Wait. That means you are giving up on universal equality. That in turn
means you can't write a fully generic contains method on lists
anymore. And that to me goes way beyond the charter for 2.8.

Cheers

milessabin
Joined: 2008-08-11,
User offline. Last seen 33 weeks 3 days ago.
Re: equality questions

On Wed, Sep 23, 2009 at 3:55 PM, martin odersky wrote:
> On Wed, Sep 23, 2009 at 4:50 PM, Miles Sabin wrote:
>> breakage will be examples of the form,
>>
>>  val l = 23L
>>  // ...
>>  if (l == 0) ...
>>           ^
>>  Proposed error: equality between Long and Int
>>
>> but these are trivially fixable, and the simplest fix that shuts the
>> compiler up is also the semantically correct one.
>
> Wait. That means you are giving up on universal equality. That in turn
> means you can't write a fully generic contains method on lists
> anymore. And that to me goes way beyond the charter for 2.8.

You're right, that's no good.

This would have to be a warning rather than an error ... in which case
Paul is going down the right route.

Cheers,

Miles

Copyright © 2012 École Polytechnique Fédérale de Lausanne (EPFL), Lausanne, Switzerland