Quantcast

== and equals

classic Classic list List threaded Threaded
8 messages Options
uwe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

== and equals

uwe
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: [hidden email]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: == and equals

Paul Phillips-3
On Sun, Jun 20, 2010 at 02:18:04AM +0200, [hidden email] 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.

--
Paul Phillips      | The most dangerous man to any government is the man who
Protagonist        | is able to think things out [...] Almost inevitably he
Empiricist         | comes to the conclusion that the government he lives under
ha! spill, pupil   | is dishonest, insane, intolerable.   -- H. L. Mencken
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: == and equals

Paul Phillips-3
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 <[hidden email]>
To: martin odersky <[hidden email]>
Cc: [hidden email]

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

--
Paul Phillips      | Giving every man a vote has no more made men wise
Moral Alien        | and free than Christianity has made them good.
Empiricist         |     -- H. L. Mencken
slap pi uphill!    |----------* http://www.improving.org/paulp/ *----------
uwe
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: == and equals

uwe
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" <[hidden email]>
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 <[hidden email]>
> To: martin odersky <[hidden email]>
> Cc: [hidden email]
>
> 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.)
>
> --
> Paul Phillips      | Giving every man a vote has no more made men wise
> Moral Alien        | and free than Christianity has made them good.
> Empiricist         |     -- H. L. Mencken
> slap pi uphill!    |----------* http://www.improving.org/paulp/
> *----------
>
Uwe Schirmer
Software Engineer and Author

E-Mail: [hidden email]

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: == and equals

friedrich
In reply to this post by Paul Phillips-3
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

Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: == and equals

Lauri Alanko
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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: == and equals

Ross A. Baker
In reply to this post by Paul Phillips-3
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
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: == and equals

Paul Phillips-3
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.

--
Paul Phillips      | It's better to have gloved and tossed than never to
Vivid              | have played baseball.
Empiricist         |
pal, i pill push   |----------* http://www.improving.org/paulp/ *----------
Loading...