Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to define generic type in Scala?

Tags:

scala

slick

In Slick 2, we can map tables like this:

case class Cooler(id: Option[Int], minTemp: Option[Double], maxTemp: Option[Double])

/**
 * Define table "cooler".
 */
class Coolers(tag: Tag) extends Table[Cooler](tag, "cooler") {
  def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
  def minTemp = column[Double]("min_temp", O.Nullable)
  def maxTemp = column[Double]("max_temp", O.Nullable)

  def * = (id.?, minTemp.?, maxTemp.?) <> (Cooler.tupled, Cooler.unapply _)
}

object Coolers {
  val tableQuery = TableQuery[Coolers]
}

because I have a lot of tables, I want to define generic methods for them, like find, delete, update so I have to define these methods in a super class from where to extend my objects (object Coolers extends TableUtils[Coolers, Cooler]). In order to define those methods, I need tableQuery to move out of my object in this super class, so I tried it like:

abstract class TableUtils[T <: Table[A] , A] {

val tableQuery = TableQuery[T]  

}

but I receive an error on tableQuery definition:

class type required but T found

Does anybody know what I am doing wrong?

like image 901
Cristian Boariu Avatar asked Nov 20 '13 07:11

Cristian Boariu


1 Answers

When you do TableQuery[T] you are in fact calling TableQuery.apply, which is actually a macro.

The body of this macro tries to instantiate T, but in your case T has become an (unknown) type parameter that the compiler does not know how to instantiate. The problem is similar to trying to compile this:

def instantiate[T]: T = new T
// Does not compile ("class type required but T found")

The net effect is that TableQuery.apply can only be used on concrete types.

You could work around that using a type class to capture the call to TableQuery.apply (at the point where the concrete type is known) along with an implicit macro to provide an instance of this type class. Then you would have something like:

abstract class TableUtils[T <: Table[A] : TableQueryBuilder, A] {
  val tableQuery = BuildTableQuery[T]
}

Where TableQueryBuilder is the type class and BuildTableQuery is an alternate version of TableQuery.apply that will forward to the TableQueryBuilder instance to perform the actual instantiation.

I've added an implementation as part of another answer here.

It will be much easier (if less convenient) to just declare tableQuery as an abstract value and define it in every concrete derived class of TableUtils:

abstract class TableUtils[T <: Table[A] , A] {
  val tableQuery: TableQuery[T, T#TableElementType]
  // define here your helper methods operating on `tableQuery`
}
object Coolers extends TableUtils[Coolers, Cooler] {
  val tableQuery = TableQuery[Coolers]
}
like image 169
Régis Jean-Gilles Avatar answered Oct 28 '22 05:10

Régis Jean-Gilles