88

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.

Frank S. Thomas
  • 4,725
  • 2
  • 28
  • 47
John S
  • 1,695
  • 2
  • 14
  • 25
  • 4
    Maybe change the title to "How to override apply in a case class companion" – ziggystar Apr 29 '11 at 12:27
  • 2
    Don't use sugar if it does not do what you want... – Raphael Apr 29 '11 at 19:16
  • 8
    @Raphael What if you want brown sugar , i.e. we want sugar with some special attributes.. I have the precise same inquiry as the OP: case classes are v useful but it is a common enough use case to want to decorate the companion object with an additional apply. – WestCoastProjects Aug 24 '14 at 19:48
  • FYI This is fixed in scala 2.12+. Defining an otherwise conflucting apply method in the companion prevents generating the default apply method. – stewSquared May 06 '19 at 19:14

10 Answers10

92

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.

olle kullberg
  • 6,249
  • 2
  • 20
  • 31
  • 5
    The `toCharArray` call is not necessary, you could also write `s.exists(_.isLower)`. – Frank S. Thomas Apr 29 '11 at 07:06
  • 4
    BTW I think `s.forall(_.isUpper)` is easier to understand than `!s.exists(_.isLower)`. – Frank S. Thomas Apr 29 '11 at 08:07
  • thanks! This certainly works for my needs. @Frank, I agree that `s.forall(_isupper)` is easier to read. I'll use that in conjunction with @olle's suggestion. – John S Apr 29 '11 at 11:36
  • 4
    +1 for "the name "case class" implies it should be possible to extract the (unmodified) constructor arguments using `match`." – Eugen Labun Apr 06 '13 at 11:04
  • 2
    @ollekullberg You don't have to move away from using a case class (and lose all the extra goodies a case case class provides by default) to achieve the OP's desired effect. If you make two modifications, you can have your case class and eat it too! A) mark the case class as abstract and B) mark the case class constructor as private[A] (as opposed to just private). There are some other more subtle issues around extending case classes using this technique. Please see the answer I posted for more thorough details: http://stackoverflow.com/a/25538287/501113 – chaotic3quilibrium Aug 29 '14 at 19:08
28

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:

  1. For your explicit companion object:
    1. Create it using exactly the same name as your case class
      • This has access to the case class's private parts
    2. Create an apply method with exactly the same signature as the primary constructor for your case class
      • This will successfully compile once step 2.1 is completed
    3. Provide an implementation obtaining an instance of the case class using the new operator and providing an empty implementation {}
      • This will now instantiate the case class strictly on your terms
      • The empty implementation {} must be provided because the case class is declared abstract (see step 2.1)
  2. For your case class:
    1. Declare it abstract
      • Prevents the Scala compiler from generating an apply method in the companion object which is what was causing the "method is defined twice..." compilation error (step 1.2 above)
    2. Mark the primary constructor as private[A]
      • The primary constructor is now only available to the case class itself and to its companion object (the one we defined above in step 1.1)
    3. Create a readResolve method
      1. Provide an implementation using the apply method (step 1.2 above)
    4. Create a copy method
      1. Define it to have exactly the same signature as the case class's primary constructor
      2. For each parameter, add a default value using the same parameter name (ex: s: String = s)
      3. Provide an implementation using the apply method (step 1.2 below)

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.

Community
  • 1
  • 1
chaotic3quilibrium
  • 5,661
  • 8
  • 53
  • 86
  • I just posted a newer expansive solution to be more Scala idiomatic and to include using ScalaCache for easily caching case class instances (wasn't allowed to edit the existing answer per the meta rules): http://codereview.stackexchange.com/a/98367/4758 – chaotic3quilibrium Jul 28 '15 at 17:47
  • thanks for this detailed explanation. But, i am struggling to understand, why readResolve implementation is required. Because compilation also works without readResolve implementation also. – mogli Aug 26 '15 at 18:56
  • posted a seperate question : http://stackoverflow.com/questions/32236594/case-class-private-constructor-need-for-readresolve-implementation – mogli Aug 26 '15 at 21:00
12

I don't know how to override the apply method in the companion object (if that is even possible) but you could also use a special type for upper case strings:

class UpperCaseString(s: String) extends Proxy {
  val self: String = s.toUpperCase
}

implicit def stringToUpperCaseString(s: String) = new UpperCaseString(s)
implicit def upperCaseStringToString(s: UpperCaseString) = s.self

case class A(val s: UpperCaseString)

println(A("hello"))

The above code outputs:

A(HELLO)

You should also have a look at this question and it's answers: Scala: is it possible to override default case class constructor?

Community
  • 1
  • 1
Frank S. Thomas
  • 4,725
  • 2
  • 28
  • 47
7

For the people reading this after April 2017: As of Scala 2.12.2+, Scala allows overriding apply and unapply by default. You can get this behavior by giving -Xsource:2.12 option to the compiler on Scala 2.11.11+ as well.

Mehmet Emre
  • 79
  • 1
  • 4
  • 1
    What does this mean? How can I apply this knowledge to a solution? Can you provide an example? – k0pernikus Mar 13 '18 at 17:39
  • Note that unapply is not used for pattern matching case classes, which makes overriding it fairly useless (if you `-Xprint` a `match` statement you will see that it is not used). – J Cracknell Jun 28 '18 at 22:06
5

It works with var variables:

case class A(var s: String) {
   // Conversion
   s = s.toUpperCase
}

This practice is apparently encouraged in case classes instead of defining another constructor. See here.. When copying an object, you also keep the same modifications.

Community
  • 1
  • 1
Mikaël Mayer
  • 10,425
  • 6
  • 64
  • 101
4

Another idea while keeping case class and having no implicit defs or another constructor is to make the signature of apply slightly different but from a user perspective the same. Somewhere I have seen the implicit trick, but can´t remember/find which implicit argument it was, so I chose Boolean here. If someone can help me out and finish the trick...

object A {
  def apply(s: String)(implicit ev: Boolean) = new A(s.toLowerCase)
}
case class A(s: String)
Peter Schmitz
  • 5,824
  • 4
  • 26
  • 48
  • At call sites it will give you a compile error (ambiguous reference to overloaded definition). It works only if the _scala_ types are different but the same after erasure, e.g. to have two different functions for a List[Int] and a List[String]. – Mikaël Mayer Nov 28 '13 at 09:49
  • I couldn't get this solution pathway to work (with 2.11). I finally worked out why he couldn't provide his own apply method on the explicit companion object. I have detailed it in the answer I just posted: http://stackoverflow.com/a/25538287/501113 – chaotic3quilibrium Aug 27 '14 at 23:16
3

I faced the same problem and this solution is ok for me:

sealed trait A {
  def s:String
}

object A {
  private case class AImpl(s:String)
  def apply(s:String):A = AImpl(s.toUpperCase)
}

And, if any method is needed, just define it in the trait and override it in the case class.

Sean Patrick Floyd
  • 292,901
  • 67
  • 465
  • 588
0

If you're stuck with older scala where you cant override by default or you dont want to add the compiler flag as @mehmet-emre showed, and you require a case class, you can do the following:

case class A(private val _s: String) {
  val s = _s.toUpperCase
}
critium
  • 612
  • 5
  • 16
0

As of 2020 on Scala 2.13, the above scenario of overriding a case class apply method with same signature works totally fine.

case class A(val s: String)

object A {
  def apply(s: String) = new A(s.toUpperCase)
}

the above snippet compiles and runs just fine in Scala 2.13 both in REPL & non-REPL modes.

Gana
  • 111
  • 1
  • 2
-2

I think this works exactly how you want it to already. Here's my REPL session:

scala> case class A(val s: String)
defined class A

scala> object A {
     | def apply(s: String) = new A(s.toUpperCase)
     | }
defined module A

scala> A("hello")
res0: A = A(HELLO)

This is using Scala 2.8.1.final

  • 3
    It doesn't work here if I put the code into a file and try to compile it. – Frank S. Thomas Apr 29 '11 at 05:06
  • I believe I suggested something similar in an earlier answer and someone said it only works in the repl due to the way repl works. – Ben Jackson Apr 29 '11 at 06:40
  • 6
    The REPL essentially creates a new scope with each line, inside the previous one. That is why some things don't work as expected when pasted from the REPL into your code. So, always check both. – gregturn Jan 10 '13 at 20:59
  • 1
    The proper way to test the above code (which does not work) is to use :paste in the REPL to ensure both case and object are defined together. – WestCoastProjects Aug 24 '14 at 19:51