Search code examples
scalastructural-typing

Scala structural type with method where only some params are known


Given this code:

object Testy extends App {

  case class Person(
                     id: Option[Long],
                     firstName: String,
                     lastName: String,
                     address: Address)

  case class Address(id: Option[Long],
                     name: String,
                     number: Int)

  val personOrAddress:AnyRef= Person(Some(1L), "first", "last", Address(Some(1L), "street", 1))
  type HasCopyMethodWithId = _
  val newId = Some(123L)
  personOrAddress.asInstanceOf[HasCopyMethodWithId].copy(id = newId)
}

How can I implement 'type HasCopyMethodWithId' so that this code compiles and does not fail in runtime?

I've tried:

type HasCopyMethodWithId = {def copy(id: Option[Long]): AnyRef}

Solution

  • The synthetic copy method provided by case classes is one single method with all arguments of that case class, not an individual overloaded one for id, firstName etc. Therefore the structural type doesn't match.

    You could add an auxiliary method:

    case class Person(id: Option[Long],
                      firstName: String,
                      lastName: String,
                      address: Address) {
    
      def copyId(newId: Option[Long]): Person = copy(id = newId)
    }
    
    case class Address(id: Option[Long],
                       name: String,
                       number: Int) {
      def copyId(newId: Option[Long]): Address = copy(id = newId)
    }
    
    val personOrAddress: Any = 
      Person(Some(1L), "first", "last", Address(Some(1L), "street", 1))
    
    type HasCopyMethodWithId = { def copyId(id: Option[Long]): Any }
    val newId = Some(123L)
    personOrAddress.asInstanceOf[HasCopyMethodWithId].copyId(id = newId)
    

    But then almost certainly better is to provide a static type:

    trait CopyWithId { 
      type Repr 
      def copyId(id: Option[Long]): Repr
    }
    
    case class Person(id: Option[Long],
                      firstName: String,
                      lastName: String,
                      address: Address) extends CopyWithId {
    
      type Repr = Person
      def copyId(newId: Option[Long]): Person = copy(id = newId)
    }
    
    case class Address(id: Option[Long],
                       name: String,
                       number: Int) extends CopyWithId {
    
      type Repr = Address
      def copyId(newId: Option[Long]): Address = copy(id = newId)
    }
    
    val personOrAddress: CopyWithId = 
      Person(Some(1L), "first", "last", Address(Some(1L), "street", 1))
    
    val newId = Some(123L)
    personOrAddress.copyId(id = newId)