So here's the situation. I want to define a case class like so:
case class A(val s: String)
and I want to define an object to ensure that when I create instances of the class, the value for 's' is always uppercase, like so:
object A { def apply(s: String) = new A(s.toUpperCase) }
However, this doesn't work since Scala is complaining that the apply(s: String) method is defined twice. I understand that the case class syntax will automatically define it for me, but isn't there another way I can achieve this? I'd like to stick with the case class since I want to use it for pattern matching.
When you want to define some functionality related to a case class you have two possible ways to do this. The first one is to create functions directly in the case class. Note that in order to define a companion object for a class you have to set the same names for them and declare them in the same file.
Case Class can NOT extend another Case class. However, they have removed this feature in recent Scala Versions. So a Case Class cannot extend another Case class. NOTE:- In Scala Language, case-to-case inheritance is prohibited.
It can contain both state and functionality. Case classes are like data POJO in Java. classes that hold state, which can be used by functions (usually in other classes).
Case Classes You can construct them without using new. case classes automatically have equality and nice toString methods based on the constructor arguments. case classes can have methods just like normal classes.
The reason for the conflict is that the case class provides the exact same apply() method (same signature).
First of all I would like to suggest you use require:
case class A(s: String) { require(! s.toCharArray.exists( _.isLower ), "Bad string: "+ s) }
This will throw an Exception if the user tries to create an instance where s includes lower case chars. This is a good use of case classes, since what you put into the constructor also is what you get out when you use pattern matching (match
).
If this is not what you want, then I would make the constructor private
and force the users to only use the apply method:
class A private (val s: String) { } object A { def apply(s: String): A = new A(s.toUpperCase) }
As you see, A is no longer a case class
. I am not sure if case classes with immutable fields are meant for modification of the incoming values, since the name "case class" implies it should be possible to extract the (unmodified) constructor arguments using match
.
UPDATE 2016/02/25:
While the answer I wrote below remains sufficient, it's worth also referencing another related answer to this regarding the case class's companion object. Namely, how does one exactly reproduce the compiler generated implicit companion object which occurs when one only defines the case class itself. For me, it turned out to be counter intuitive.
Summary:
You can alter the value of a case class parameter before it is stored in the case class pretty simply while it still remaining a valid(ated) ADT (Abstract Data Type). While the solution was relatively simple, discovering the details was quite a bit more challenging.
Details:
If you want to ensure only valid instances of your case class can ever be instantiated which is an essential assumption behind an ADT (Abstract Data Type), there are a number of things you must do.
For example, a compiler generated copy
method is provided by default on a case class. So, even if you were very careful to ensure only instances were created via the explicit companion object's apply
method which guaranteed they could only ever contain upper case values, the following code would produce a case class instance with a lower case value:
val a1 = A("Hi There") //contains "HI THERE" val a2 = a1.copy(s = "gotcha") //contains "gotcha"
Additionally, case classes implement java.io.Serializable
. This means that your careful strategy to only have upper case instances can be subverted with a simple text editor and deserialization.
So, for all the various ways your case class can be used (benevolently and/or malevolently), here are the actions you must take:
apply
method with exactly the same signature as the primary constructor for your case class new
operator and providing an empty implementation {}
{}
must be provided because the case class is declared abstract
(see step 2.1) abstract
apply
method in the companion object which is what was causing the "method is defined twice..." compilation error (step 1.2 above) private[A]
readResolve
method copy
method s: String = s
) Here's your code modified with the above actions:
object A { def apply(s: String, i: Int): A = new A(s.toUpperCase, i) {} //abstract class implementation intentionally empty } abstract case class A private[A] (s: String, i: Int) { private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method A.apply(s, i) def copy(s: String = s, i: Int = i): A = A.apply(s, i) }
And here's your code after implementing the require (suggested in the @ollekullberg answer) and also identifying the ideal place to put any sort of caching:
object A { def apply(s: String, i: Int): A = { require(s.forall(_.isUpper), s"Bad String: $s") //TODO: Insert normal instance caching mechanism here new A(s, i) {} //abstract class implementation intentionally empty } } abstract case class A private[A] (s: String, i: Int) { private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method A.apply(s, i) def copy(s: String = s, i: Int = i): A = A.apply(s, i) }
And this version is more secure/robust if this code will be used via Java interop (hides the case class as an implementation and creates a final class which prevents derivations):
object A { private[A] abstract case class AImpl private[A] (s: String, i: Int) def apply(s: String, i: Int): A = { require(s.forall(_.isUpper), s"Bad String: $s") //TODO: Insert normal instance caching mechanism here new A(s, i) } } final class A private[A] (s: String, i: Int) extends A.AImpl(s, i) { private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method A.apply(s, i) def copy(s: String = s, i: Int = i): A = A.apply(s, i) }
While this directly answers your question, there are even more ways to expand this pathway around case classes beyond instance caching. For my own project needs, I have created an even more expansive solution which I have documented on CodeReview (a StackOverflow sister site). If you end up looking it over, using or leveraging my solution, please consider leaving me feedback, suggestions or questions and within reason, I will do my best to respond within a day.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With