Jeśli dobrze rozumiem, co chcesz, to aby móc rozszerzyć MyBase
po prostu definiowanie M
i T
ale bez wyraźnie instancję TableQuery
w każdej klasie pochodnej.
Korzystanie odbicie naprawdę nie jest rozwiązaniem, ponieważ normalnie używasz TableQuery.apply
za to (jak w val query = TableQuery[MyTable]
), a to jest realizowane poprzez makro, tak masz „czas pracy vs kompilacji” problem.
Jeśli naprawdę potrzebujesz, aby być cechą (w przeciwieństwie do klasy), to nie widzę żadnego realnego rozwiązania. Jeśli jednak można przekształcić w klasę i obrócić M
i w parametry typu (zamiast abstrakcyjnych typów), to istnieje co najmniej jedno rozwiązanie. Jak wskazałem w innym pokrewnym pytaniu (How to define generic type in Scala?), można zdefiniować klasę typu (na przykład TableQueryBuilder
), aby przechwycić połączenie do TableQuery.apply
(w miejscu, w którym znany jest konkretny typ) wraz z niejawnym makrem (na przykład TableQueryBuilder.builderForTable
), aby dostarczyć instancję klasy tego typu. Następnie można zdefiniować metodę (na przykład TableQueryBuilder.build
), aby faktycznie utworzyć instancję TableQuery
, która po prostu przekazuje zadanie do klasy typu.
// NOTE: tested with scala 2.11.0 & slick 3.0.0
import scala.reflect.macros.Context
import scala.language.experimental.macros
object TableQueryBuilderMacro {
def createBuilderImpl[T<:AbstractTable[_]:c.WeakTypeTag](c: Context) = {
import c.universe._
val T = weakTypeOf[T]
q"""new TableQueryBuilder[$T]{
def apply(): TableQuery[$T] = {
TableQuery[$T]
}
}"""
}
}
trait TableQueryBuilder[T<:AbstractTable[_]] {
def apply(): TableQuery[T]
}
object TableQueryBuilder {
implicit def builderForTable[T<:AbstractTable[_]]: TableQueryBuilder[T] = macro TableQueryBuilderMacro.createBuilderImpl[T]
def build[T<:AbstractTable[_]:TableQueryBuilder](): TableQuery[T] = implicitly[TableQueryBuilder[T]].apply()
}
Efekt netto jest to, że nie trzeba już znać konkretną wartość typu T
aby móc instancji TableQuery[T]
, pod warunkiem, że masz ukrytą wystąpienie TableQueryBuilder[T]
zakres. Innymi słowy, możesz przesunąć potrzebę poznania konkretnej wartości T
aż do momentu, w którym faktycznie ją znasz.
MyBase
(obecnie klasa) może następnie być realizowane tak:
class MyBase[M, T <: Table[M] : TableQueryBuilder] {
lazy val query: TableQuery[T] = TableQueryBuilder.build[T]
}
I można wtedy przedłużyć go bez konieczności explcitly zadzwonić TableQuery.apply
:
class Coffees(tag: Tag) extends Table[(String, Double)](tag, "COFFEES") {
def name = column[String]("COF_NAME")
def price = column[Double]("PRICE")
def * = (name, price)
}
class Derived extends MyBase[(String, Double), Coffees] // That's it!
Co się dzieje, jest to, że w konstruktorze Derived
, niejawna wartość dla TableQueryBuilder[Coffees]
jest domyślnie przekazana do konstruktora MyBase
.
Powodem, dlaczego nie można zastosować ten wzór jeśli MyBase
były cechą jest całkiem przyziemne: konstruktorzy trait nie mogą mieć parametrów, nie mówiąc już parametry ukryte, więc nie byłoby niejawny sposób przekazać instancję TableQueryBuilder
.
Może to głupie pytanie, ale jaki jest cel tworzenia instancji TableQuery w tej cechie (ciężko jest zrozumieć wartość, ponieważ będzie ona inna dla każdego typu, który działa z tą cechą) - być może dodanie tego może dodać więcej kontekstu, aby ktoś mógł uzyskać lepszą odpowiedź. –