scalaclassextension-methodsimplicitimplicit-class

Creating a type-sensitive function without changing the parent trait or case classes


Suppose I have two classes, Person and Business, that are extended by the trait Entity.

trait Entity
case class Person(name: String) extends Entity
case class Business(id: String) extends Entity

Assuming I cannot change Entity, Person and Business (they are in a different file and not to be changed) how can I define a function, say a printEntity, that prints the field name or id, depending on the entity? For example, given instances of Person and Business, how can I do something like this:

object Main extends App {
  val person1: Person = Person("Aaaa Bbbb")
  val business1: Business = Business("0001")
  
  // How can I do something like this?
  person1.printEntity // would call a function that executes println(id)
  business1.printEntity // would call a function that executes println(name)
}

Any ideas are appreciated! Sorry for the lack of context, I am still learning!


Solution

  • This is done via so called "extension methods". In scala 2 this is achieved using implicit wrapper class:

    trait Entity
    case class Person(name: String) extends Entity
    case class Business(id: String) extends Entity
    
    implicit class PersonWrapper(val p: Person) extends AnyVal {
      def printEntity(): Unit = {
        println(p.name)
      }
    }
    
    implicit class BusinessWrapper(val b: Business) extends AnyVal {
      def printEntity(): Unit = {
        println(b.id)
      }
    }
    
    val person1: Person = Person("Aaaa Bbbb")
    val business1: Business = Business("0001")
    
    person1.printEntity()
    business1.printEntity()
    
    // prints:
    //Aaaa Bbbb
    //0001
    

    Note, x.printEntity can be called without parentheses, but, by convention, methods with Unit result type and side effects should be called with explicit empty parentheses.


    UPD: As @DmytroMitin pointed out, you should extend implicit wrapper classes from AnyVal. This allows the compiler to avoid actually allocating wrapper class instances at runtime, improving performance.