Distinguishing case class members

143 views
Skip to first unread message

Peter Empen

unread,
Dec 26, 2015, 5:22:08 AM12/26/15
to scala-language
Currently all parameters of the first parameter list go into the generated equals/hashCode/copy/unapply methods.
This works well for most cases but sometimes we need to exclude some parameters from equals/hashCode while keeping them copy and matchable.

Now, to relieve programmers from the burden of defining custom equals/hashCode in such situations, it would be great to have some direct means to advise the compiler which parameters should not go into equals/hashCode.

One approach could be to provide a specific member-level annotation for this purpose.
Another approach could be to let val-/non-implicit-members of any secondary parameter list become a parameter of copy automatically.
Maybe you have even a better approach.

What do you think about this proposal?

Simon Schäfer

unread,
Dec 26, 2015, 9:49:23 AM12/26/15
to scala-l...@googlegroups.com
Write a macro for it. Way easier than hacking the compiler.
--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Daniel Armak

unread,
Dec 26, 2015, 2:54:18 PM12/26/15
to scala-l...@googlegroups.com
Or move the parameters which shouldn't affect equals into a different parameter list, and simulate the original param lists with extra constructors.

Daniel Armak

Lex Spoon

unread,
Dec 27, 2015, 7:20:04 PM12/27/15
to scala-l...@googlegroups.com
On Sat, Dec 26, 2015 at 9:49 AM, Simon Schäfer <ma...@antoras.de> wrote:
Write a macro for it. Way easier than hacking the compiler.

Based on a quick web search, "ScalaEquals" by Alex DiCarlo looks highly relevant.  There may be others.  -Lex


Seth Tisue

unread,
Jan 8, 2016, 4:51:59 PM1/8/16
to scala-language
We all like `case class` in Scala because it avoids boilerplate, but there's another enormous reason to like it: it tells the reader of your code what to expect.

You propose adding more flexibility to `case class`. If anything, I feel like `case class` already offers too much flexibility, and I would prefer it offered less! The less flexibility it offers, the more I know what I'm getting when I'm reading somebody else's code and I see `case class`.

Admittedly, the flexibility you want is already available by manually overriding `equals` and `hashCode`. So you might argue that your proposal doesn't actually add flexibility, but merely reduce boilerplate. But I'm not sure it's desirable to reduce boilerplate for something that I'd rather not see in Scala code at all. Or at least, hardly ever.

I admit to having occasionally — with mixed feelings — used the ability to exclude parameters from equals/hashCode/toString participation by putting them in a case class's parameter list. Isn't that enough? Do we really need more flavors of this? Can you explain your use case?

Peter Empen

unread,
Jan 10, 2016, 6:41:00 AM1/10/16
to scala-language

Seth,


In my experience Scala users opt for case classes more often that for simple classes. Some of them not even consider pure classes. Most of them go for case classes even if just one of the case class extensions like ‘apply’ is ‘needed’. Further, nothing new for you, some wide-spread Scala libraries, like serialization libraries, also encourage the usage of case classes by providing special support for them.

 

Given this situation, a discussion about more maturity for case classes deserves some interest, at least I hope so.

 

I don’t see opinions like ‘The less flexibility it offers, the more I know what I'm getting’ are always valid arguments. If they were, one could for example ask whether optional ‘val’s in primary constructor parameter lists were also against simplicity. Further, don’t default ‘val’s in case class constructors, as opposed to non-default with all other templates, break simplicity? Yes they do for a good reason: usability.

 

But let’s get back to my original suggestion.

 

The reason I’m asking to make thoughts about additional flexibility for case classes on this list is that the compiler has all information about how to add that case class goodies, anyway. So why should the programmer be expected to use experimental macros or to inflate her case classes with otherwise boilerplate code if the compiler could do it easily? Maybe that’s also the reason why you had ‘mixed feelings’ one time or another.

 

You are asking for my use case. One use case for my suggestion are those thousands of case classes in enterprise apps that are just case classes because the developers like to instantiate them without spelling out ‘new’. Though less frequently, they also like pattern matching against them and, even less frequently, to call ‘copy’ for a new instance with a minor difference to the origin.

 

Now imagine at least 10 percent of the case classes finding their way into a Set or Map sooner or later. Another 10 percent go into equality based comparisons. Until then, it was fine to place all members into a single parameter list, 5 to 15 members on average. But usually just one of these members is a key - for instance, think of case classes reflecting database objects. At this point you are simply locked.

 

IMO a clean language design should really take care of both kinds of case class members: Those that go into apply/unapply/copy… and those, a subset of the first, that additionally go into equals/hashCode and not lump them together. (Those to be ignored in the generated methods are already covered by additional parameter lists.)

Naftoli Gugenheim

unread,
Jan 10, 2016, 8:07:46 AM1/10/16
to scala-l...@googlegroups.com
On Sat, Dec 26, 2015 at 5:22 AM Peter Empen <empen...@gmail.com> wrote:
Currently all parameters of the first parameter list go into the generated equals/hashCode/copy/unapply methods.
This works well for most cases but sometimes we need to exclude some parameters from equals/hashCode while keeping them copy and matchable.

Now, to relieve programmers from the burden of defining custom equals/hashCode in such situations, it would be great to have some direct means to advise the compiler which parameters should not go into equals/hashCode.

Rather than putting them in the primary parameter list and  defining a custom equals/hashCode, it seems a lot easier to put them in a secondary parameter list, which requires solving two problems as you mentioned, copy and pattern matching. For copy, it seems you can create your own copy method with a different name (copy2 say) for the extra parameters; that should not be too difficult. And for pattern matching you can just use a guard, or select them from the matched object ( a match { case x @ X(1, _) => x.c })

It would be nice to automate the above, but I think doing so is an easier target than changing the way the compiler generates copy and pattern matching.
 
 

One approach could be to provide a specific member-level annotation for this purpose.
Another approach could be to let val-/non-implicit-members of any secondary parameter list become a parameter of copy automatically.
Maybe you have even a better approach.

What do you think about this proposal?

--

Matt Farmer

unread,
Jan 10, 2016, 8:12:58 AM1/10/16
to scala-l...@googlegroups.com
I’m concerned that the rarity of needing to do this may make it unreasonable to maintain such a feature. I’ve been doing Scala since 2011 and have maybe needed to do that once? How frequently do you find yourself wanting this?


Matt Farmer | Blog | Twitter
GPG: CD57 2E26 F60C 0A61 E6D8  FC72 4493 8917 D667 4D07

Justin du coeur

unread,
Jan 13, 2016, 10:56:33 AM1/13/16
to scala-l...@googlegroups.com
On Sun, Jan 10, 2016 at 8:12 AM, Matt Farmer <ma...@frmr.me> wrote:
I’m concerned that the rarity of needing to do this may make it unreasonable to maintain such a feature. I’ve been doing Scala since 2011 and have maybe needed to do that once? How frequently do you find yourself wanting this?

Yeah, agreed -- in about five years of full-time Scala, and very heavy use of case classes, I don't think I've ever wanted this particular feature.  It seems like a rare edge case to me, and not a necessarily desireable one.

And to Seth's point -- I think we have a real problem, as a community, that there's never been a clear, universally-accepted statement of what case classes are *for*.

-- Do they exist for the "case" concept, making unapply cheap and easy?
-- Are they just the sum of the functionality, a way to get apply, equals and hashCode for free?
-- Or are they a signal that this class is immutable, and that all that functionality makes sense *because* this is an immutable class described by its parameters?

Personally, I subscribe to the third of those; from what he's said, I suspect Seth is coming from a similar viewpoint.  I think of the functionality of case classes as directly derived from the notion that this object is precisely described by its immutable parameters -- that's the mental model, and the driver of how it works.  When I see a case class, I *expect* it to be immutable, and I *expect* all of its parameters to factor into equals.  It's possible to violate those expectations, but that's a recipe for bugs.

Sadly, the horse has *long* since bolted the barn on this one: "impure" case classes are, in practice, pretty common.  But I don't think they should be driving our thinking.  I agree with what I think Seth is saying here: with 20/20 hindsight, it's actually a bit of a language design bug that case classes are more flexible than they ought to be, because that impedes reasoning about them easily.  We shouldn't encourage that...

Oliver Ruebenacker

unread,
Jan 13, 2016, 11:25:29 AM1/13/16
to scala-l...@googlegroups.com

     Hello,

  If a field is used for apply/unapply, but not for equals/hashCode, you would have objects that are equal but don't match the same patterns. Doesn't that sound really awefull?

  e.g.

  case class A(x: Int, @DoNotUseForEquals y: Int)  // not real code

  val a1 = A(1, 2)
  val a2 = A(1, 3)  //  a1 == a2

  a2 match {
    case A(_, 2) => println("y is 2")  //  matches a1, but not a2
    case _ => println("y is not 2")
  }

     Best, Oliver

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



--
Oliver Ruebenacker
Senior Software Engineer, Diabetes Portal, Broad Institute

Seth Tisue

unread,
Jan 13, 2016, 5:22:55 PM1/13/16
to scala-language
in this vein, see also Rüdiger Klaehn's answer at http://stackoverflow.com/a/34719624/86485 ("People frequently use case classes and then try to customize/abuse them to do something else than what case classes are supposed to do...")

Oliver Ruebenacker

unread,
Jan 13, 2016, 5:50:40 PM1/13/16
to scala-l...@googlegroups.com

     Hello,

  Why are mutable case classes bad?

     Best, Oliver

On Wed, Jan 13, 2016 at 5:22 PM, Seth Tisue <se...@tisue.net> wrote:
in this vein, see also Rüdiger Klaehn's answer at http://stackoverflow.com/a/34719624/86485 ("People frequently use case classes and then try to customize/abuse them to do something else than what case classes are supposed to do...")

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

Sébastien Doeraene

unread,
Jan 13, 2016, 6:51:02 PM1/13/16
to scala-l...@googlegroups.com
Hi,

Because it is usually a bad thing for `x == y` to change over time. If your case class is mutable, its auto-generated `equals` will cause `x == y` to to behave differently during the life of `x` and/or `y`. Mutable classes should always have reference equality, i.e., `x == y` if and only if `x eq y`, otherwise you break referential transparency, one of the most useful properties of functional programming.

Cheers,
Sébastien

Haoyi Li

unread,
Jan 13, 2016, 7:29:00 PM1/13/16
to scala-l...@googlegroups.com
I disagree, x == y changing over time makes perfect sense to me. If I have two empty buffers, they're equal. I add something to one of them, they're unequal. I add that thing to the other, they're equal again.

Sure, it makes sets and dictionaries unhappy if you put these mutable structs into them, but if you don't do that equality works great. If it's already mutable anyway, you've already broken all your referential transparency functional programming stuff, but there is no "additional badness" from having == also be mutable

Nils Kilden-Pedersen

unread,
Jan 13, 2016, 9:18:27 PM1/13/16
to scala-l...@googlegroups.com
On Wed, Jan 13, 2016 at 4:50 PM, Oliver Ruebenacker <cur...@gmail.com> wrote:

     Hello,

  Why are mutable case classes bad?

The same reason mutable classes are bad. They are harder to reason about when shared.
Reply all
Reply to author
Forward
0 new messages