Generated equals and hashCode behave unexpectedly when case class has an array field

308 views
Skip to first unread message

Clint Gilbert

unread,
Dec 3, 2013, 5:36:10 PM12/3/13
to scala...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I was surprised to see this earlier:

Welcome to Scala version 2.10.3 (OpenJDK 64-Bit Server VM, Java 1.7.0_25).
Type in expressions to have them evaluated.
Type :help for more information.

scala> final case class C(b: Array[Int])
defined class C

scala> val a = Array(1,2,3)
a: Array[Int] = Array(1, 2, 3)

scala> C(a) == C(a)
res0: Boolean = true

scala> C(Array(1,2,3)) == C(Array(1,2,3))
res1: Boolean = false

//eh?

scala> C(a).hashCode == C(a).hashCode
res2: Boolean = true

scala> C(Array(1,2,3)).hashCode == C(Array(1,2,3)).hashCode
res3: Boolean = false

//eh?

It appears array fields are being compared by reference. This is
different from how fields of other types are compared in case classes.

I know arrays are a messy special case on the JVM, but am I wrong to
expect that case classes' equals and hashCode methods work by-value,
regardless of the type of fields?

Using java.util.Arrays.{hashCode,equals} is a fairly easy workaround,
but I'd rather not write my own hashCode and equals methods, since the
generated ones are such a nice feature of case classes.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)

iEYEARECAAYFAlKeXNoACgkQ0GFaTS4nYxvHIQCeJTWzB96p+QgKsCXiRzXcSbpy
z8kAoIvdmrc+CoN95f9jyCPs44voLzM4
=dXVA
-----END PGP SIGNATURE-----

Roman Janusz

unread,
Dec 3, 2013, 6:16:06 PM12/3/13
to scala...@googlegroups.com

I know arrays are a messy special case on the JVM, but am I wrong to
expect that case classes' equals and hashCode methods work by-value,
regardless of the type of fields?


You are not wrong and that is exactly how it works. Case classes always compare their fields by value. The problem is that equals/hashCode methods of arrays themselves implement comparison by reference:

scala> Array(1,2,3).equals(Array(1,2,3))
res0: Boolean = false

scala> Array(1,2,3).hashCode == Array(1,2,3).hashCode
res1: Boolean = false


Remember that arrays are MUTABLE and, of course, case classes are immutable by definition. This means that you absolutely should not use arrays in case class fields.
There is plenty of immutable collection types like List or Vector that you can successfully use instead.

√iktor Ҡlang

unread,
Dec 3, 2013, 6:33:13 PM12/3/13
to Roman Janusz, scala-user

+1

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.

Clint Gilbert

unread,
Dec 3, 2013, 7:22:50 PM12/3/13
to scala...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

I'll get a few things out of the way: I don't think mutable case
classes (or mutable classes period) are necessarily a good idea. I'm
very aware of all the immutable sequences in the standard lib. For
reasons that aren't relevant or interesting, an Array is most
appropriate in my code. I know that using mutable objects as hash
keys is a bad idea. I know that ScalaRunTime._hashCode and
MurmurHash3.productHash are implemented in terms of objects' hashCode
methods.

Now that that's over with, I'm not sure you're right about case
classes being immutable by definition. Here's me making a mutable one:

scala> case class C(var x: Int)
defined class C

scala> val c = C(123)
c: C = C(123)

scala> c.x = 42
c.x: Int = 42

scala> c
res0: C = C(42)

Whether or not one *should* make a mutable case class, it appears that
one can. With this in mind, is it right to have different equality
semantics for

case class A(var x: Int) //by value

and

case class B(stuff: Array[Byte]) //by reference

because the types of the fields in A and B are different? Would it
break much existing code if case classes compared by value in all cases?
> -- You received this message because you are subscribed to the
> Google Groups "scala-user" group. To unsubscribe from this group
> and stop receiving emails from it, send an email to
> scala-user+...@googlegroups.com. For more options, visit
> https://groups.google.com/groups/opt_out.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)

iEYEARECAAYFAlKeddoACgkQ0GFaTS4nYxsB8QCgxa3K+fC89t5wsNQDyQZTNffi
ORYAoK3KKDEvncuD8WQTvrEF8ITuqXkQ
=mzw1
-----END PGP SIGNATURE-----

√iktor Ҡlang

unread,
Dec 3, 2013, 7:39:19 PM12/3/13
to Clint Gilbert, scala-user
It would make a huge performance impact.

Cheers,



--
Cheers,

Viktor Klang

Director of Engineering

Twitter: @viktorklang

Gilbert, Clint

unread,
Dec 3, 2013, 8:56:32 PM12/3/13
to √iktor Ҡlang, scala-user
Fair enough. Was performance the reason for the current case class behavior, or did someone just forget to special-case arrays in the standard library hashing methods? (In general, forgetting a special case seems very plausible.)

________________________________________
From: √iktor Ҡlang [viktor...@gmail.com]
Sent: Tuesday, December 03, 2013 7:39 PM
To: Gilbert, Clint
Cc: scala-user
Subject: Re: [scala-user] Re: Generated equals and hashCode behave unexpectedly when case class has an array field
> scala-user+...@googlegroups.com<mailto:scala-user%2Bunsu...@googlegroups.com>. For more options, visit
> https://groups.google.com/groups/opt_out.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)

iEYEARECAAYFAlKeddoACgkQ0GFaTS4nYxsB8QCgxa3K+fC89t5wsNQDyQZTNffi
ORYAoK3KKDEvncuD8WQTvrEF8ITuqXkQ
=mzw1
-----END PGP SIGNATURE-----

--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com<mailto:scala-user%2Bunsu...@googlegroups.com>.
For more options, visit https://groups.google.com/groups/opt_out.



--
Cheers,


Viktor Klang
Director of Engineering
Typesafe<http://www.typesafe.com/>

Twitter: @viktorklang

Jason Zaugg

unread,
Dec 4, 2013, 2:00:33 AM12/4/13
to Clint Gilbert, scala...@googlegroups.com
There are certainly times when you prefer to stash arrays in your (case) classes, usually motivated by primitives and performance. I've found a safer way to do it is to declare the parameter of type s.c.mutable.WrappedArray. That gives you access to the raw array but provides structural equality.

I find that it is better to think of case classes as a boilerplate generator (and have an understanding of what they generate), rather than to think of them as inherently immutable. Sure, some of the boilerplate (copy) makes using immutable classes much more convenient, but ultimately in scala immutabily is up to you. 

Jason
--
You received this message because you are subscribed to the Google Groups "scala-user" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-user+...@googlegroups.com.

Jason Zaugg

unread,
Dec 4, 2013, 2:03:02 AM12/4/13
to Clint Gilbert, scala...@googlegroups.com
I do admit that this is a pretty easy trap to fall into. We've recently been discussing the need for better lint tools, this is one of he many things it ought to flag  

Clint Gilbert

unread,
Dec 4, 2013, 2:04:22 PM12/4/13
to Jason Zaugg, scala...@googlegroups.com
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Thanks Jason! The s.c.mutable.WrappedArray trick looks like just what
I need.

On 12/04/2013 02:00 AM, Jason Zaugg wrote:
> I find that it is better to think of case classes as a boilerplate
> generator (and have an understanding of what they generate), rather
> than to think of them as inherently immutable. Sure, some of the
> boilerplate (copy) makes using immutable classes much more
> convenient, but ultimately in scala immutabily is up to you.

Thanks for saying this too. It's how I've been thinking about case
classes lately.
> -- You received this message because you are subscribed to the
> Google Groups "scala-user" group. To unsubscribe from this group
> and stop receiving emails from it, send an email to
> scala-user+...@googlegroups.com <javascript:;>. For more
> options, visit https://groups.google.com/groups/opt_out.
>
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.11 (GNU/Linux)

iEYEARECAAYFAlKffLUACgkQ0GFaTS4nYxvjqACff9SO9nu1+5svTbOZTch8CPQw
Zs4AmQHPpl0kbs8FPbUIZg0HyWYAE6kg
=hu6R
-----END PGP SIGNATURE-----
Reply all
Reply to author
Forward
0 new messages