Scala 反射实例化scala.slick.lifted.TableQuery

Scala reflection to instantiate scala.slick.lifted.TableQuery

我有这个基本特征

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

其中 TableQueryscala.slick.lifted.TableQuery

我的子类实例化 TableQuery 像这样:

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

我想实例化基本特征中的 TableQuery,可能使用 lazy val,即

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

我一直在思考,但运气不佳。

如果我没理解错的话,你想要的是能够扩展 MyBase 通过简单地定义 MT 而不必在每个派生的 class.

中显式实例化 TableQuery

使用反射并不是一个真正的选择,因为通常你使用 TableQuery.apply 为此(如 val query = TableQuery[MyTable]),这是通过宏实现的, 所以你有一个 "runtime vs compile-time" 问题。

如果您绝对需要 MyBase 成为特征(而不是 class),那么我看不到任何可行的解决方案。 但是,如果您可以将 MyBase 变成 class MT 变成类型参数(而不是抽象类型),那么至少有一个解决方案。 正如我在另一个相关问题 (How to define generic type in Scala?) 中暗示的那样,您可以 定义一个类型 class(比如说 TableQueryBuilder)来捕获对 TableQuery.apply 的调用(在具体类型已知的地方)以及一个隐式宏(比如 TableQueryBuilder.builderForTable)提供 这种类型的实例 class。然后,您可以定义一个方法(比如 TableQueryBuilder.build)来实际实例化 TableQuery,它只会将作业委托给 class.

类型
// 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)可以这样实现:

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 实例。