2015-05-12 10 views
8

mam tę cechę podstawowąScala odbicie instancji scala.slick.lifted.TableQuery

trait MyBase { 
    type M 
    type T <: Table[M] 
    val query: TableQuery[T] 
} 

Gdzie TableQuery jest scala.slick.lifted.TableQuery

Moje podklasy instancję TableQuery tak:

type M = Account 
type T = AccountsTable 
val query = TableQuery[T] 

I chcesz utworzyć instancję TableQuery w podstawowej funkcji, najlepiej przy użyciu lazy val, tj.

lazy val query: TableQuery[T] = { 
    ... 
} 

Bawiłem się refleksją, ale nie miałem szczęścia.

+0

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ź. –

Odpowiedz

8

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.

+0

Dziękuję za poświęcenie czasu na złożenie tej odpowiedzi razem. Wiele świetnych informacji tutaj. – ic3b3rg

+0

Mimo że jest to świetne rozwiązanie, wciąż staram się znaleźć abstrakcyjne proste wyrażenie 'db.run (query.result)', ponieważ nie można go rozwiązać. Czy istnieje jakiś sposób, aby w ogóle zaimplementować coś w rodzaju Mapera Danych? Dzięki – ramsvidor

+0

Nie jestem pewien, czy rozumiem Twój problem. Możesz zamieścić odpowiednie pytanie z odpowiednim fragmentem kodu demonstrującym problem. –