Как определить универсальный тип в 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.