angelcervera
angelcervera

Reputation: 4209

Defining a generic to be a case class

In this example, I want the generic T to be a case class and a DAOEntity with id, so in the abstract implementation, I can use the copy method.

How to define it?

trait DAOEntity {
  def id: String
}

// How to define this generic to force the use of a `case class` to have access to `copy`?
abstract class DAO[T <: DAOEntity] {
  def storeInUppercase(entity: T): T = entity.copy(id = entity.id)
}

case class MyEntity(id: String) extends DAOEntity

class MyEntityDAO extends DAO[MyEntity] {
  // Other stuff
}

Upvotes: 3

Views: 356

Answers (1)

There is no way to know if a type is a case class or not.
And even if there was, you won't get the copy method. The language doesn't provide a way to abstract over constructor; thus neither copy and factories (apply on companions) by extension. Which makes sense, what would be the type signature of such function?

What you can do instead is create a factory-like typeclass and ask for that:

trait DAOFactory[T <: DAOEntity] {
  def copy(oldEntity: T, newId: String): T
}
object DAOFactory {
  def instance[T <: DAOEntity](f: (T, String) => T): DAOFactory[T] =
    new DAOFactory[T] {
      override final def copy(oldEntity: T, newId: String): T =
        f(oldEntity, newId)
    }
}

Which can be used like this:

abstract class DAO[T <: DAOEntity](implicit factory: DAOFactory[T]) {
  def storeInUppercase(entity: T): T =
    factory.copy(
      oldEntity = entity,
      newId = entity.id.toUpperCase
    )
}

And entities would provide the instance like this:

final case class MyEntity(id: String, age: Int) extends DAOEntity
object MyEntity {
  implicit final val MyEntityFactory: DAOFactory[MyEntity] =
    DAOFactory.instance {
      case (oldEntity, newId) =>
        oldEntity.copy(id = newId)
    }
}

// This compile thanks to the instance in the companion object.
object MyEntityDAO extends DAO[MyEntity]

You can see the code running here.

Upvotes: 6

Related Questions