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.