How to define generic type in Scala?

2020-06-18 04:01发布

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?

标签: scala slick
2条回答
家丑人穷心不美
2楼-- · 2020-06-18 04:19

Here is one solution:

At first, define this to avoid class type issue..

class Service[T <: Table[_]](path: String, cons: Tag => T){

  lazy val db = Database.forConfig(path)

  def query = TableQuery[T](cons)
}

Then use it this way, Post is sub class of Table:

object Abcd {

  object Def extends Service[Post]("mydb", abc) {



    def test = {


      //db

      val q = query.drop(1).take(20)
      val r = db.run(q.result)

      println(q.result.statements.head)
      println(r)

      r

    }
  }

  private def abc(tag: Tag) = new Post(tag)

}

This solution tested ok in slick 3.x, and Play slick 1.x, since the slick 2.0 Query.scala comply to slick 3.0 Query.scala, this might work at 2 too.

查看更多
Root(大扎)
3楼-- · 2020-06-18 04:24

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]
}
查看更多
登录 后发表回答