Как определить универсальный тип в Scala?

В Slick 2 мы можем отобразить таблицы следующим образом:

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]
}

потому что у меня много таблиц, я хочу определить для них общие методы, такие как find, delete, update поэтому я должен определить эти методы в суперклассе, откуда расширять мои объекты (object Coolers extends TableUtils[Coolers, Cooler]). Чтобы определить эти методы, мне нужно tableQuery выйти из моего объекта в этом суперклассе, поэтому я попробовал это так:

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

val tableQuery = TableQuery[T]  

}

но я получаю ошибку на tableQuery определение:

class type required but T found

Кто-нибудь знает, что я делаю не так?

2 ответа

Решение

Когда вы делаете TableQuery[T] вы на самом деле звоните TableQuery.apply, который на самом деле является макросом.

Тело этого макроса пытается создать экземпляр T, но в вашем случае T стал (неизвестным) параметром типа, который компилятор не знает, как создать экземпляр. Проблема похожа на попытку скомпилировать это:

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

Чистый эффект заключается в том, что TableQuery.apply может использоваться только на конкретных типах.

Вы можете обойти это, используя класс типов для захвата вызоваTableQuery.apply (в точке, где известен конкретный тип) вместе с неявным макросом для предоставления экземпляра этого типа. Тогда у вас будет что-то вроде:

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

куда TableQueryBuilder это класс типа и BuildTableQuery это альтернативная версия TableQuery.apply что перешлет к TableQueryBuilder экземпляр для выполнения фактической реализации.

Я добавил реализацию как часть другого ответа здесь.

Будет гораздо проще (если менее удобно) просто объявить tableQuery как абстрактное значение и определить его в каждом конкретном производном классе 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]
}

Вот одно из решений:

Сначала определите это, чтобы избежать проблемы с типом класса.

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

  lazy val db = Database.forConfig(path)

  def query = TableQuery[T](cons)
}

Затем используйте его следующим образом: Post является подклассом 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)

}

Это решение протестировало нормально в slick 3.x и Play slick 1.x, так как slick 2.0 Query.scala соответствует slick 3.0 Query.scala, это также может работать на 2.

Другие вопросы по тегам