I'd like to use case classes to describe the types of my data more expressively so that to benefit from higher static correctness. The goal is to have 100% static certainty that any Age value in existence always contains a valid human age (leaving aside the fact that encapsulation rules can be bypassed using reflection).
For example, instead of using Int to store ages of persons, I have:
case class Age(x: Int) extends AnyVal
def mkAge(x: Int) = if (0 <= x && x <= 150) Some(Age(x)) else None
def unwrapAge(age: Age) = age.x
however, this implementation suffers from the fact that Age can still be instantiated without going through mkAge and unwrapAge.
Next, I tried to make the constructor private:
case class Age private(x: Int) extends AnyVal
object Age {
def make(x: Int) = if (0 <= x && x <= 150) Some(Age(x)) else None
def unwrap(age: Age) = age.x
}
however, while this does prevent Age from being instantiated using new (e.g. new Age(3)), the autogenerated apply(x: Int) in object Age is still easily accessible.
So, here's the question: how to hide both the constructor as well as the default apply method in the companion object from anything but Age.make or mkAge?
I'd like to avoid having to use a regular (non-case) class and correctly replicate the auto-generated methods in class Age and object Age manually.
I think Age just don't need to be a case class. Because it is a Value Class you don't need to override equals and hashcode, also it has only one field so there is no benefit from copy constructor. And you can do nothing with apply() in companion object. If you still want to use case class you can add require but it doesn't solve you problem of instantiation.