Se ho capito bene, ciò che si desidera è di essere in grado di estendere MyBase
semplicemente definendo M
e T
, ma senza dover istanziare esplicitamente il TableQuery
in ogni classe derivata.
utilizzando la riflessione non è davvero un'opzione perché normalmente si utilizza TableQuery.apply
per questo (come in val query = TableQuery[MyTable]
), e questo è attuato mediante una macro, quindi hai un "runtime vs tempo di compilazione" questione.
Se è assolutamente necessario che MyBase
sia un tratto (al contrario di una classe), quindi non vedo alcuna soluzione praticabile. Tuttavia, se è possibile convertire e in virgola mobile M
e T
in parametri di tipo (anziché in tipi astratti), tuttavia esiste almeno una soluzione. Come ho accennato in un'altra domanda correlata (How to define generic type in Scala?), è possibile definire una classe di tipo (ad esempio TableQueryBuilder
) per acquisire la chiamata a TableQuery.apply
(nel punto in cui è noto il tipo concreto) insieme a una macro implicita (ad esempio) a fornire un'istanza di questa classe di tipi. È quindi possibile definire un metodo (ad esempio TableQueryBuilder.build
) per creare un'istanza dello TableQuery
, che delegherà semplicemente il lavoro alla classe del tipo.
// 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()
}
L'effetto netto è che non è necessario più conoscere il valore concreto del tipo T
al fine di essere in grado di creare un'istanza di un TableQuery[T]
, purché si disponga un'istanza implicita di TableQueryBuilder[T]
portata. In altre parole, è possibile spostare la necessità di conoscere il valore concreto di fino al punto in cui lo si conosce effettivamente.
MyBase
(ora una classe) che possono essere attuati in questo modo:
class MyBase[M, T <: Table[M] : TableQueryBuilder] {
lazy val query: TableQuery[T] = TableQueryBuilder.build[T]
}
e si può quindi estendere senza la necessità di explcitly chiamare 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!
Cosa succede qui è che nel costruttore Derived
, un valore implicito per TableQueryBuilder[Coffees]
è implicitamente passato al costruttore MyBase
.
Il motivo per cui non è possibile applicare questo schema se MyBase
erano un tratto è abbastanza banale: costruttori tratto non possono avere parametri, per non parlare di parametri impliciti, quindi non ci sarebbe modo implicito di passare l'istanza TableQueryBuilder
.
Forse questa è una domanda stupida, ma qual è lo scopo di istanziare il TableQuery nel tratto (è difficile capire il valore, perché, sarà diverso per ogni tipo che funziona con il tratto) - forse aggiungendo questo potrebbe aggiungere più contesto per qualcuno per fornire una risposta migliore. –