« Return to Thread: equals in case class does not seem to be working...

Re: equals in case class does not seem to be working...

by Kevin Wright-4 :: Rate this Message:

Reply to Author | View in Thread

This isn't limited to subverted case classes, it's true of *any* mutable object in a hashmap
(strictly speaking, it's only true of any object with a mutatable hashkey, so any mutability should be restricted to volatile uses - such as caching - and not used in hashing or equality tests)

So long as you steer away from hashmaps and other such issues, I still believe that there is a valid use case for generating equality and extractor boilerplate code in certain mutable objects.



On Wed, Jul 8, 2009 at 4:35 PM, Daniel Sobral <dcsobral@...> wrote:
Let me expand a bit on this. Case classes serve a specific purpose, which is to implement algebraic types in an object-oriented way. To do this, a bunch of methods are automatically created, and just a little bit of magic compiler glue is applied. But for those methods to work, some assumptions have to be made, and making parts of the case class mutable violate those assumptions. See here, for instance:
 
scala> case class T(var i: Int) // Our mutable case class
defined class T
 
scala> T(0)  // One instance of it
res4: T = T(0)
 
scala> res4.hashCode  // It's hashcode
res5: Int = 3444
 
scala> scala.collection.immutable.HashMap(res4 -> 'a)  // Let's use it as a key
res9: scala.collection.immutable.HashMap[T,Symbol] = Map(T(0) -> 'a)
 
scala> res9(res4)  // Can we retrieve it?
res10: Symbol = 'a
 
scala> res4.hashCode  // Check the hashCode
res11: Int = 3444
 
scala> res4.i = 1  // Let's mutate it
 
scala> res4.hashCode  // How's the hashCode now?
res12: Int = 3445
 
scala> res9(res4)  // Can we retrieve it now?
java.util.NoSuchElementException: key not found: T(1)
        at scala.collection.generic.MapTemplate$class.default(MapTemplate.scala:177)
        at scala.collection.immutable.HashMap.default(HashMap.scala:30)
        at scala.collection.generic.MapTemplate$class.apply(MapTemplate.scala:92)
        at scala.collection.immutable.HashMap.apply(HashMap.scala:30)
        at .<init>(<console>:9)
        at .<clinit>(<console>)
        a...

scala> res9(T(0))  // Well, we changed T(0) into a T(1), so let's retrieve a T(0)
java.util.NoSuchElementException: key not found: T(0)
        at scala.collection.generic.MapTemplate$class.default(MapTemplate.scala:177)
        at scala.collection.immutable.HashMap.default(HashMap.scala:30)
        at scala.collection.generic.MapTemplate$class.apply(MapTemplate.scala:92)
        at scala.collection.immutable.HashMap.apply(HashMap.scala:30)
        at .<init>(<console>:9)
        at .<clinit>(<console>)
        a...

scala> T(0)  // That didn't work. Let's make a new T(0)
res15: T = T(0)
 
scala> res15.hashCode  // Check hashCode...
res16: Int = 3444
 
scala> res9(res15)  // Can we get it?
java.util.NoSuchElementException: key not found: T(0)
        at scala.collection.generic.MapTemplate$class.default(MapTemplate.scala:177)
        at scala.collection.immutable.HashMap.default(HashMap.scala:30)
        at scala.collection.generic.MapTemplate$class.apply(MapTemplate.scala:92)
        at scala.collection.immutable.HashMap.apply(HashMap.scala:30)
        at .<init>(<console>:10)
        at .<clinit>(<console>)
        ...
scala> res9.keys foreach println  // What _are_ the keys?
<console>:9: warning: method keys in trait MapTemplate is deprecated: use `keysIterator' instead
       res9.keys foreach println
            ^
T(1)
 
scala>

 
On Wed, Jul 8, 2009 at 12:17 PM, Daniel Sobral <dcsobral@...> wrote:
Why did you expect this to work? Case classes are supposed to be immutable.


On Tue, Jul 7, 2009 at 10:39 PM, Alexandre <alexmsmartins@...> wrote:
Hi to you all

First of all I'm a less than a year scala hobbyist (e. g. user) and
this is my first post to this list. So bare with me. :)
I was trying to do unit testing and I thought that using
assertEquals() in Junit 4 with objects from the same case class with
exactly the same content wold cut it. Yet, I run into some troubles
and ended up doing this small snippet in the scala shell that shows me
it does not work.
Am I doing anything wrong or is this to be expected?
If it is to be expected could you explain/point to some reading that
makes it clear why.

Thanks in advance
Alexandre Martins

------------------------------------------------------------------------------------------

scala> import scala.xml.Node
import scala.xml.Node

scala> import scala.xml.Elem
import scala.xml.Elem

scala> import scala.reflect.BeanProperty
import scala.reflect.BeanProperty

scala> import scala.reflect.BeanProperty
import scala.reflect.BeanProperty

scala> import java.util.Date
import java.util.Date

scala> import scala.reflect.BeanInfo
import scala.reflect.BeanInfo

scala> /**
   |  * Represents a user of WikiModels
   |  */

scala> @BeanInfo
   | case class User(usrName:String,
   |            pssword:String,
   |            frstName:String,
   |            lstName:String,
   |            mail:String ) extends DataModel {
   |
   |     var userName = usrName
   |     var password = pssword
   |     var firstName = frstName
   |     var lastName = lstName
   |     var emailAddress = mail
   |
   |     def this() = {
   |         this("","","","","")
   |     }
   |
   |     /**
   |      * generates a XML representation of the user, excluding password
   |      * @return the XML representing the user
   |      */
   |     def toXML:Node =
   |     <user>
   |         <username>{userName}</username>
   |         <!--<password>{password}</password>-->
   |         <firstName>{firstName}</firstName>
   |         <lastName>{lastName}</lastName>
   |         <email>{emailAddress}</email>
   |     </user>
   |
   |     def extractXML(xml:Node):Unit = {
   |         xml match {
   |             case <username>{contents}</username> =>
this.userName = contents.text
   |             case <firstName>{contents}</firstName> =>
this.firstName = contents.text
   |             case <lastName>{contents}</lastName> =>
this.lastName = contents.text
   |             case <email>{contents}</email> => this.emailAddress
= contents.text
   |             case <user>{c @ _ *}</user> => for (val child <- c)
extractXML(child)
   |             case _ => { }
   |         }
   |     }
   | }
defined class User

scala> var u = User("alex","alexp","Alexandre",
"Martins","alexmsmartins@...")
u: User = User(alex,alexp,Alexandre,Martins,alexmsmartins@...)

scala> var d = new User()
d: User = User(,,,,)

scala> u == d
res2: Boolean = false

scala> u
res3: User = User(alex,alexp,Alexandre,Martins,alexmsmartins@...)

scala> d
res4: User = User(,,,,)

scala> d.extractXML(  u.toXML)

scala> d.password = u.password

scala> d.password
res18: String = alexpscala> d
res7: User = User(,,,,)

scala> d.toXML == u.toXML
res8: Boolean = true

scala> d == u
res9: Boolean = false



--
Daniel C. Sobral

Something I learned in academia: there are three kinds of academic reviews: review by name, review by reference and review by value.



--
Daniel C. Sobral

Something I learned in academia: there are three kinds of academic reviews: review by name, review by reference and review by value.

 « Return to Thread: equals in case class does not seem to be working...