4
votes

I have this case class with a lot of parameters:

case class Document(id:String, title:String, ...12 more params.. , keywords: Seq[String]) 

For certain parameters, I need to do some string cleanup (trim, etc) before creating the object.

I know I could add a companion object with an apply function, but the LAST thing I want is to write the list of parameters TWICE in my code (case class constructor and companion object's apply).

Does Scala provide anything to help me on this?

2
Not really, as it kind of works against the notion of a case class. A normal class constructor or overriding apply would have to do it. – Michael Zajac
Use Builder pattern – rarry

2 Answers

3
votes

My general recommendations would be:

  • Your goal (data preprocessing) is the perfect use case of a companion object -- so it is maybe the most idiomatic solution despite the boilerplate.

  • If the number of case class parameters is high the builder pattern definitely helps, since you do not have to remember the order of the parameters and your IDE can help you with calling the builder member functions. Using named arguments for the case class constructor allows you to use a random argument order as well but, to my knowledge, there is not IDE autocompletion for named arguments => makes a builder class slightly more convenient. However using a builder class raises the question of how to deal with enforcing the specification of certain arguments -- the simple solution may cause runtime errors; the type-safe solution is a bit more verbose. In this regard a case class with default arguments is more elegant.

There is also this solution: Introduce an additional flag preprocessed with a default argument of false. Whenever you want to use an instance val d: Document, you call d.preprocess() implemented via the case class copy method (to avoid ever typing all your arguments again):

case class Document(id: String, title: String, keywords: Seq[String], preprocessed: Boolean = false) {
  def preprocess() = if (preprocessed) this else {
    this.copy(title = title.trim, preprocessed = true) // or whatever you want to do
  }
}

But: You cannot prevent a client to initialize preprocessed set to true.

Another option would be to make some of your parameters a private val and expose the corresponding getter for the preprocessed data:

case class Document(id: String, title: String, private val _keywords: Seq[String]) {
  val keywords = _keywords.map(kw => kw.trim)
}

But: Pattern matching and the default toString implementation will not give you quite what you want...

1
votes

After changing context for half an hour, I looked at this problem with fresh eyes and came up with this:

case class Document(id: String, title: String, var keywords: Seq[String]) {
  keywords = keywords.map(kw => kw.trim)
}

I simply make the argument mutable adding var and cleanup data in the class body.

Ok I know, my data is not immutable anymore and Martin Odersky will probably kill a kitten after seeing this, but hey.. I managed to do what I want adding 3 characters. I call this a win :)