- About Scala
- Documentation
- Code Examples
- Software
- Scala Developers
== and equals
Sun, 2010-06-20, 01:18
Hi
I have a very strange effect with == and equals. As far as I understand
should both methods give the same result for objects that are not null.
I will paste a session with the interactive shell from today:
----------------------------
scala> val a = 1
a: Int = 1
scala> val b = 1.0
b: Double = 1.0
scala> a == b
res90: Boolean = true
scala> b == a
res91: Boolean = true
scala> a equals b
res92: Boolean = false
scala> b equals a
res93: Boolean = false
----------------------------
Am I wrong or is there something wrong?
Best regards
Uwe Schirmer
Uwe Schirmer
Software Engineer and Author
E-Mail: uwe@schirmer.org
Sun, 2010-06-20, 01:47
#2
Re: == and equals
FYI here is a summary of ==/equals I sent to -internals on Apr 13. I
didn't get a response on this but I'm pretty sure it's in the ballpark
of accurate since I implemented most of it.
From: Paul Phillips
To: martin odersky
Cc: scala-internals@listes.epfl.ch
Here is a kind of off the top of my head attempt to spec out equality
and hash codes. I don't really speak spec-ese but this is written in
the pidgin spec-ese within my grasp. Does this look approximately
correct? (Anyone else feel free to chime in on that point.) What if
anything would you like me to do with it?
Resolution of x == y
====================
1) Null values will not cause NPEs.
2) Nothing is == to null except null.
3) All objects must be == to themselves.
The first three conditions are summarized in this initial expansion of 'x == y', which the compiler may or may not inline. All user-defined equals methods are responsible for preserving invariants 2 and 3.
if (x eq y) true
else if (x eq null) false
else // remainder of algorithm
4) If the static type of the left hand side allows for the possibility that it is a boxed or unboxed primitive numeric type (any of Byte, Short, Int, Long, Float, Double, or Char) then: go to step 5.
If the static type definitively excludes those types, then: the result is x.equals(y).
5) If the static types of both operands are primitive types, then: the result is that of the primitive comparison, exactly as performed in java.
If the static types are identical final types (for instance, both are java.lang.Longs) then the result is x.equals(y).
In all other cases, both operands are boxed if necessary and a method in BoxesRunTime is called. (The method will be semantically equivalent to BoxesRunTime.equals, but a different method may be chosen to avoid repeating the above tests.)
BoxesRuntime.equals
===================
All of the preceding logic is preserved, and then it proceeds as follows, where 'x' remains the left hand side operand and 'y' the right.
1) Runtime instance checks will be done to determine the types of the operands, with the following resolutions. (Resolutions represent the semantics, not necessarily the implementation.)
1a) If both sides of the comparison are boxed primitives, then they are unboxed and the primitive comparison is performed as in java.
1b) If 'x' is a class implementing the scala.math.ScalaNumber trait, then the result is x.equals(y).
1c) If 'x' is a boxed primitive and 'y' is a class implementing the scala.math.ScalaNumber trait, then the result is y.equals(x).
1d) Otherwise, the result is x.equals(y).
hashCode and ##
===============
The unification of primitives and boxed types in scala necessitates measures to preserve the equality contract: equal objects must have equal hash codes. To accomplish this a new method is introduced on Any:
def ##: Int
This method should be called in preference to hashCode by all scala software which consumes hashCodes. (One need not use or even be aware of it unless implementing something which depends on hashCodes -- to define an object's hashCode, overridding hashCode remains the mechanism.)
The default implementation of ## is simply to call hashCode:
def ##: Int = this.hashCode()
In the case of numeric types however, it selectively alters hash codes to support the == algorithm given above. The guarantees provided by ## are as follows. "Numbers" are the aforementioned primitives (boxed or unboxed) and any standard scala library class implementing ScalaNumber.
1) If x and y are whole Numbers in the range Int.MinValue to Int.MaxValue, then (x == y) implies (x.## == y.##). The value of ## for all Numbers in that range is equal to the result of .toInt on that Number.
2) If x and y are Numbers and either or both is fractional, then the guarantee in 1) applies if both are in the range Short.MinValue to Short.MaxValue.
3) If x and y are Numbers and neither 1) nor 2) applies, the implication is preserved on a best-effort basis, but cannot be preserved generally given the fuzziness introduced in primitive equality at the borders. (For instance given a large Float, a java primitive Float/Double comparison may return true for 2^10 different Double values, and similar issues arise with Longs and Doubles.)
Sun, 2010-06-20, 07:57
#3
Re: == and equals
Thank you, you are great.
I had a look in some of the Books about Scala and all stated that == is
a test for null and calling equals.
MAny thanks
Uwe
On Sat, 19 Jun 2010 17:33 -0700, "Paul Phillips"
wrote:
> FYI here is a summary of ==/equals I sent to -internals on Apr 13. I
> didn't get a response on this but I'm pretty sure it's in the ballpark
> of accurate since I implemented most of it.
>
>
> From: Paul Phillips
> To: martin odersky
> Cc: scala-internals@listes.epfl.ch
>
> Here is a kind of off the top of my head attempt to spec out equality
> and hash codes. I don't really speak spec-ese but this is written in
> the pidgin spec-ese within my grasp. Does this look approximately
> correct? (Anyone else feel free to chime in on that point.) What if
> anything would you like me to do with it?
>
> Resolution of x == y
> ====================
>
> 1) Null values will not cause NPEs.
>
> 2) Nothing is == to null except null.
>
> 3) All objects must be == to themselves.
>
> The first three conditions are summarized in this initial expansion of 'x
> == y', which the compiler may or may not inline. All user-defined equals
> methods are responsible for preserving invariants 2 and 3.
>
> if (x eq y) true
> else if (x eq null) false
> else // remainder of algorithm
>
> 4) If the static type of the left hand side allows for the possibility
> that it is a boxed or unboxed primitive numeric type (any of Byte, Short,
> Int, Long, Float, Double, or Char) then: go to step 5.
>
> If the static type definitively excludes those types, then: the result is
> x.equals(y).
>
> 5) If the static types of both operands are primitive types, then: the
> result is that of the primitive comparison, exactly as performed in java.
>
> If the static types are identical final types (for instance, both are
> java.lang.Longs) then the result is x.equals(y).
>
> In all other cases, both operands are boxed if necessary and a method in
> BoxesRunTime is called. (The method will be semantically equivalent to
> BoxesRunTime.equals, but a different method may be chosen to avoid
> repeating the above tests.)
>
> BoxesRuntime.equals
> ===================
>
> All of the preceding logic is preserved, and then it proceeds as follows,
> where 'x' remains the left hand side operand and 'y' the right.
>
> 1) Runtime instance checks will be done to determine the types of the
> operands, with the following resolutions. (Resolutions represent the
> semantics, not necessarily the implementation.)
>
> 1a) If both sides of the comparison are boxed primitives, then they are
> unboxed and the primitive comparison is performed as in java.
>
> 1b) If 'x' is a class implementing the scala.math.ScalaNumber trait, then
> the result is x.equals(y).
>
> 1c) If 'x' is a boxed primitive and 'y' is a class implementing the
> scala.math.ScalaNumber trait, then the result is y.equals(x).
>
> 1d) Otherwise, the result is x.equals(y).
>
> hashCode and ##
> ===============
>
> The unification of primitives and boxed types in scala necessitates
> measures to preserve the equality contract: equal objects must have equal
> hash codes. To accomplish this a new method is introduced on Any:
>
> def ##: Int
>
> This method should be called in preference to hashCode by all scala
> software which consumes hashCodes. (One need not use or even be aware of
> it unless implementing something which depends on hashCodes -- to define
> an object's hashCode, overridding hashCode remains the mechanism.)
>
> The default implementation of ## is simply to call hashCode:
>
> def ##: Int = this.hashCode()
>
> In the case of numeric types however, it selectively alters hash codes to
> support the == algorithm given above. The guarantees provided by ## are
> as follows. "Numbers" are the aforementioned primitives (boxed or
> unboxed) and any standard scala library class implementing ScalaNumber.
>
> 1) If x and y are whole Numbers in the range Int.MinValue to
> Int.MaxValue, then (x == y) implies (x.## == y.##). The value of ## for
> all Numbers in that range is equal to the result of .toInt on that
> Number.
>
> 2) If x and y are Numbers and either or both is fractional, then the
> guarantee in 1) applies if both are in the range Short.MinValue to
> Short.MaxValue.
>
> 3) If x and y are Numbers and neither 1) nor 2) applies, the implication
> is preserved on a best-effort basis, but cannot be preserved generally
> given the fuzziness introduced in primitive equality at the borders.
> (For instance given a large Float, a java primitive Float/Double
> comparison may return true for 2^10 different Double values, and similar
> issues arise with Longs and Doubles.)
>
Sun, 2010-06-20, 10:07
#4
Re: == and equals
Just for the record. This is correct code !?
Welcome to Scala version 2.8.0.RC5 (Java HotSpot(TM) 64-Bit Server VM, Java
1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.
scala> val no= Array.ofDim(1)
no: Array[Nothing] = Array(null)
scala> println(no(0)==null)
true
regards
esser
Sun, 2010-06-20, 10:37
#5
Re: Re: == and equals
On Sun, Jun 20, 2010 at 02:02:10AM -0700, friedrich wrote:
> Just for the record. This is correct code !?
> scala> val no= Array.ofDim(1)
> no: Array[Nothing] = Array(null)
Nothing should not be inhabited, even by null. This is a compiler bug.
> scala> println(no(0)==null)
> true
Apart from the above, this is OK.
Lauri
Mon, 2010-06-21, 03:57
#6
Re: == and equals
Paul Phillips-3 wrote:
>
> Resolution of x == y
> ====================
>
> 1) Null values will not cause NPEs.
>
> 2) Nothing is == to null except null.
>
> 3) All objects must be == to themselves.
>
> The first three conditions are summarized in this initial expansion of 'x
> == y', which the compiler may or may not inline. All user-defined equals
> methods are responsible for preserving invariants 2 and 3.
>
> if (x eq y) true
> else if (x eq null) false
> else // remainder of algorithm
>
If the left-hand side is a null with a static type compatible with
java.lang.Number and the right-hand side is a primitive, then I get an NPE.
This contradicts the first rule above. Is this the expected behavior? The
below is from 2.8.0.RC6:
scala> null == 42
res0: Boolean = false
scala> (null:java.lang.Integer) == 42
java.lang.NullPointerException
at scala.runtime.BoxesRunTime.equalsNumNum(Unknown Source)
at scala.runtime.BoxesRunTime.equalsNumObject(Unknown Source)
scala> (null:java.lang.Integer) == new java.lang.Integer(42)
res2: Boolean = false
scala> (null:String) == 42
res3: Boolean = false
scala> (null:java.lang.Integer) == "foo"
res4: Boolean = false
Thanks,
Ross
Mon, 2010-06-21, 04:07
#7
Re: Re: == and equals
On Sun, Jun 20, 2010 at 07:54:44PM -0700, Ross A. Baker wrote:
> If the left-hand side is a null with a static type compatible with
> java.lang.Number and the right-hand side is a primitive, then I get an
> NPE. This contradicts the first rule above. Is this the expected
> behavior?
Very much a bug, which I will fix immediately.
On Sun, Jun 20, 2010 at 02:18:04AM +0200, uwe@scala-info.org wrote:
> I have a very strange effect with == and equals. As far as I
> understand should both methods give the same result for objects that
> are not null.
...except primitives.
> Am I wrong or is there something wrong?
You are incorrect. I don't know if the spec has been updated. With the
exception of a couple corner case issues, comparing two primitives
(boxed or unboxed) with == should always give the result you would have
gotten by comparing those values as unboxed primitives. When you call
equals directly, you are skipping all that softening logic and instead
treated to java's theory that two boxed values of different types are
always unequal.