Отражение Scala для создания экземпляра scala.slick.lifted.TableQuery
У меня есть эта базовая черта
trait MyBase {
type M
type T <: Table[M]
val query: TableQuery[T]
}
куда TableQuery
является scala.slick.lifted.TableQuery
Мои подклассы инстанцируют TableQuery
вот так:
type M = Account
type T = AccountsTable
val query = TableQuery[T]
Я хотел бы создать экземпляр TableQuery
в базовой черте, возможно, с помощью lazy val
т.е.
lazy val query: TableQuery[T] = {
...
}
Я играл с отражением, но мне не повезло.
1 ответ
Если я правильно понимаю, что вы хотите, чтобы иметь возможность расширить MyBase
просто определяя M
а также T
но без необходимости явно создавать TableQuery
в каждом производном классе.
Использование отражения на самом деле не вариант, потому что обычно вы используете TableQuery.apply
для этого (как в val query = TableQuery[MyTable]
), и это реализуется с помощью макроса, поэтому у вас есть проблема "время выполнения против времени компиляции".
Если вам абсолютно необходимо MyBase
быть чертой (в отличие от класса), тогда я не вижу никакого жизнеспособного решения. Однако, если вы можете включить MyBase
в класс и превратить M
а также T
в параметры типа (вместо абстрактных типов), то есть хотя бы одно решение. Как я уже говорил в другом связанном вопросе ( Как определить универсальный тип в Scala?), Вы можете определить класс типов (скажем, TableQueryBuilder
) чтобы перехватить звонок TableQuery.apply
(в точке, где конкретный тип известен) вместе с неявным макросом (скажем, TableQueryBuilder.builderForTable
) предоставить экземпляр этого типа класса. Затем вы можете определить метод (скажем, TableQueryBuilder.build
) на самом деле создать экземпляр TableQuery
, который будет просто делегировать работу классу типа.
// 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()
}
Чистый эффект состоит в том, что вам больше не нужно знать конкретное значение типа T
для того, чтобы иметь возможность создать экземпляр TableQuery[T]
при условии, что у вас есть неявный экземпляр TableQueryBuilder[T]
в рамках. Другими словами, вы можете избавиться от необходимости знать конкретную ценность T
до точки, где вы на самом деле знаете это.
MyBase
(теперь класс) может быть реализован так:
class MyBase[M, T <: Table[M] : TableQueryBuilder] {
lazy val query: TableQuery[T] = TableQueryBuilder.build[T]
}
И затем вы можете расширить его без необходимости явного вызова 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!
Что происходит здесь, это то, что в Derived
конструктор, неявное значение для TableQueryBuilder[Coffees]
неявно передается MyBase
конструктор.
Причина, по которой вы не можете применить этот шаблон, если MyBase
если черта довольно обыденная: конструкторы черт не могут иметь параметров, не говоря уже о неявных параметрах, поэтому не будет никакого неявного способа передачи TableQueryBuilder
пример.