Ending the confusion of private case class constructor in Scala 2.13 or 2.14


#1

There is a bug open about this (only mentioning .copy, but .apply should likely be treated alike).

The bug comments mention “… the principled thing is to give the synthetic copy method the same access modifiers as the primary constructor. This isn’t a source compatible change, however, so …”

The reason this deserves to get attention, is that it can cut out some confusion from Scala. As you can see in the little scouting I did about this, it’s been brought up multiple times, under multiple Scala versions (2.9, 2.10, 2.11, 2.12).

The path of least surprise would mean that a case class applies the same access to its generated methods (.apply, .copy), as its constructor has.

How can I promote this for 2.13 or 2.14?


#2

Since this is a non-trivial language change which affects compatibility, I think the way to go here would be writing a SIP: https://docs.scala-lang.org/sips/sip-submission.html


#3

Thanks. Do you know if the current behaviour is by design, or just by chance?


#5

I wish I knew what choice words the previous respondent had for the SIP process, but I see Seth has already promised on the ticket to merge behind the SIP committee’s back. It’s nice to have high friends in high places.

I think this was by design. If you know the syntax for private case class constructor, you probably know how to subvert this behavior. On the ticket, scabug says subvert but probably meant pervert. To finish the quote, “it will be some work to slowly put the genie back in the bottle.”

To answer the question directly: Fork it! and fork it.


#6

I removed the entry because my questions were answered in the SIP process guide.

What ticket do you mean? (link please)

On the ticket, scabug says subvert but probably meant pervert.

Instead of pervert, you probably meant “revert”? :wink: Anyhow, I’d like to check that ticket to see if someone’s already on this.


#7

I don’t know if you ran across this workaround in your research, but sealed abstract case class Foo private (...) suppresses generation of apply and copy, which allows you to have validated factory methods on the companion object.

This does nothing to alleviate confusion, obviously. But it’s workable until the bigger issue gets sorted out.


#8

Was not aware of that, thanks. It fits in nicely with the use cases I have had for this.

But as you say, this may still be worth fixing at some version. Will incorporate the workaround in my repo which highlights/studies this terrain. Thanks for it! :slight_smile:


#9

@tpolecat I had a look, but unfortunately with the sealed abstract approach, one also prevents creation of such case class instances, within the companion object (which is my original use case). So it gets too complicated. It’s added to the case-class-gym repo, now.


#10

I think you have to create a private/anonymous subclass.


#11

It works, you need to instantiate an abstract subclass. Constructor is hidden, no default apply, no copy. Pattern matching works.

sealed abstract case class Foo private (s: String, n: Int)
object Foo {
  def apply(s: String): Foo =
    new Foo(s, s.length) {}
}
 
Foo("x", 42)     // nope
new Foo("x", 42) // nope

val a = Foo("x") 
// a: Foo = Foo("x", 1)

a.copy(n = 42)   // nope

val Foo(s, n) = a 
// s: String = "x"
// n: Int = 1