Search code examples
scalainheritancetypeclassshapelesscase-class

Scala: Abstracting the toString function of case classes


Suppose I have a case class defined as follows:

case class User(name: String, age: Int)

I would like to override its toString method like this:

 case class User(name: String, age: Int) {
    override def toString: String = 
      s"name = $name
        age = $age"

So that if I run print(user.toString), I get:

name = nameOfUser
age = ageOfUser

Now I have another class called Computer and it is defined as

case class Computer(make: String, RAM: int, clockSpeed: Double)

I would like to print the same for all its values. I would like something like:

  make = makeOfComputer
  RAM = RAMOfComputer
  clockSpeed = speedOfComputer

Instead of copying, pasting and adapting the above toString function to the Computer class, I'd rather abstract this away so that it can be used by any case class.

Some ideas I have are that I can use

 CaseClassType.unapply(caseClassInstance).get.productIterator.toList

to get the values of the case class and

 classOf[CaseClass].getDeclaredFields.map(_.getName)

to get the names of the fields. What this means is that I can find a list of all the values in a case class as well as a list of all the field names without knowing the actual structure of the case class at all.

Once I have these two collections, I can recursively go through them to create the string. I was thinking something like the below could work but unfortunately scala doesn't allow case classes to inherit from other case classes.

case class StringifiableCaseClass(){
  override def toString: String =
     //get the values and the fieldnames and create a string

All I'd have to do is get the case classes to extend StringifiableCaseClass and they would be able to create the right string.

The reason why I want to use a case class as the base type is that I can use

 classOf[this.type]...etc

and

  this.type.unapply(this)...etc

in StringifiableCaseClass.

Any ideas on how I can achieve what I am after given I cannot extend case classes with other case classes?


Solution

  • The base implementation does not have to be a case class. Just make it a trait, and then your case classes can all extend it.

     trait Stringification { self: Product => 
        override def toString() = 
          getClass.getDeclaredFields
          .zip(productIterator.toSeq)
          .map { case (a, b) => s"${a.getName}=$b" }
          .mkString("\n")
     }
    

    and then

      case class Foo(foo: String, bar: Int) extends Stringification
      case class Bar(foo: Foo, bar: String) extends Stringification
    

    ... etc.