Отражение 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 пример.

Другие вопросы по тегам