I'm writing a type-safe code and want to replace apply()
generated for case class
es with my own implementation. Here it is:
import shapeless._
sealed trait Data
case object Remote extends Data
case object Local extends Data
case class SomeClass(){
type T <: Data
}
object SomeClass {
type Aux[TT] = SomeClass { type T = TT }
def apply[TT <: Data](implicit ev: TT =:!= Data): SomeClass.Aux[TT] = new SomeClass() {type T = TT}
}
val t: SomeClass = SomeClass() // <------------------ still compiles, bad
val tt: SomeClass.Aux[Remote.type] = SomeClass.apply[Remote.type] //compiles, good
val ttt: SomeClass.Aux[Data] = SomeClass.apply[Data] //does not compile, good
I want to prohibit val t: SomeClass = SomeClass()
from compiling. Is it possible to do somehow except do not SomeClass
to be case class
?
There is a solution that is usually used if you want to provide some smart constructor and the default one would break your invariants. To make sure that only you can create the instance you should:
apply
new
.copy
This is achieved by this interesing patten:
sealed abstract case class MyCaseClass private (value: String)
object MyCaseClass {
def apply(value: String) = {
// checking invariants and stuff
new MyCaseClass(value) {}
}
}
Here:
abstract
prevents generation of .copy
and apply
sealed
prevents extending this class (final
wouldn't allow abstract
)private
constructor prevents using new
While it doesn't look pretty it's pretty much bullet proof.
As @LuisMiguelMejíaSuárez pointed out this is not necessary in your exact case, but in general that could be used to deal with edge cases of case class
with a smart constructor.
UPDATE:
In Scala 3 you only need to do
case class MyCaseClass private (value: String)
and it will prevent usage of: apply, new and copy from outside of this class and its companion.
This behavior was ported to Scala 2.13 with option -Xsource:3
enabled. You have to use at least 2.13.2 as in 2.13.1 this flag doesn't fix the issue.