0
votes

Im trying to construct a generic DAO that is instantiated with a concrete class of either type:

  1. Regular
  2. Snappy <: Regular

I currently have a trait DAO that describes the available methods, get/create/upsert. At compile time I would like to change/define each of the methods based on the type parameter i am passing. Here is some code:

trait Regular {
    id: int
}

trait Snap extends Regular {
    isSnappy: Boolean = true
}

trait DAO[T<: Regular] {
  def get(id: String)(implicit x: Param,ct: ClassTag[T]): Future[Either[String, T]] 
}

if the trait is Regular:

 def get(id:String)(implicit x: Param,ct: ClassTag[T]): Future[Either[String, T]] = {
   //Handle regular trait flow
 }

if the trait is Snap <: Regular:

 def get(id:String)(implicit x: Param,ct: ClassTag[T]): Future[Either[String, T]] = {
   //Handle snappy
 }

Both defs would return the same type T.

I want to be able to change the def at compile time, where I have knowledge of the Type parameter in the DAO. Then at run time I want to be able to instantiate the class but have it handle the def based on the type being passed.

Im not sure how to handle this in scala, whether its a macro based solution, overloaded methods, some type of implicit defined for the Dao. Any direction would be much appreciated! Im not sure if this is a unique question but I have read through as much as I could regarding compile time definition based on type parameters while still being able to refer to the same trait (DAO in this case)

1
Despite having functional programming constructs, Scala also allows us to program in an Object Oriented fashion, which seems closer to what you're already doing. Don't you think this behavior should be encapsulated inside your Regular / Snap traits instead of the DAO?Rodrigo Vedovato
The behaviour occurs within the dao, and would be something like insert here, or insert here & here. It is not something a Regular or Snap class would understand or should define within itself.athomassi

1 Answers

1
votes

You probably want a typeclass for this situation. There is lots to read about this out there, but here is a sample based on a cut-down version of your code.

First define a class with all the operations that are needed:

trait DaoOps[T] {
  def get(id: String): Int
  def create(id: String): T
  def upsert(id: String): Int
}

Then create an implicit instance of DaoOps with the implementation for each type:

trait Regular {
    def id: Int
}

object Regular {
  // DAO implementation for instances of Regular type
  implicit object dao extends DaoOps[Regular] {
    def get(id: String): Int = ???
    def create(id: String): Regular = ???
    def upsert(id: String): Int = ???
  }
}

trait Snap extends Regular {
  def isSnappy: Boolean = true
}

object Snap {
  // DAO implementation for instances of Snap type
  implicit object dao extends DaoOps[Snap] {
    def get(id: String): Int = ???
    def create(id: String): Snap = ???
    def upsert(id: String): Int = ???
  }
}

Finally, use the typeclass in the main class to select the implementation for the particular type that is being used:

trait DAO[T <: Regular] {
  // Select the ops for the actual type of T
  def get(id: String)(implicit ops: DaoOps[T]): Int =
    ops.get(id)
}

The implicit objects don't have to be in the companion class, but the compiler will look for them there so it avoids having to explicitly bring them into scope.