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]
}
其中 TableQuery
是 scala.slick.lifted.TableQuery
我的子类实例化 TableQuery
像这样:
type M = Account
type T = AccountsTable
val query = TableQuery[T]
我想实例化基本特征中的 TableQuery
,可能使用 lazy val
,即
lazy val query: TableQuery[T] = {
...
}
我一直在思考,但运气不佳。
如果我没理解错的话,你想要的是能够扩展
MyBase
通过简单地定义 M
和 T
而不必在每个派生的 class.
中显式实例化 TableQuery
使用反射并不是一个真正的选择,因为通常你使用 TableQuery.apply
为此(如 val query = TableQuery[MyTable]
),这是通过宏实现的,
所以你有一个 "runtime vs compile-time" 问题。
如果您绝对需要 MyBase
成为特征(而不是 class),那么我看不到任何可行的解决方案。
但是,如果您可以将 MyBase
变成 class 和 将 M
和 T
变成类型参数(而不是抽象类型),那么至少有一个解决方案。
正如我在另一个相关问题 (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
实例。
我有这个基本特征
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] = {
...
}
我一直在思考,但运气不佳。
如果我没理解错的话,你想要的是能够扩展
MyBase
通过简单地定义 M
和 T
而不必在每个派生的 class.
TableQuery
使用反射并不是一个真正的选择,因为通常你使用 TableQuery.apply
为此(如 val query = TableQuery[MyTable]
),这是通过宏实现的,
所以你有一个 "runtime vs compile-time" 问题。
如果您绝对需要 MyBase
成为特征(而不是 class),那么我看不到任何可行的解决方案。
但是,如果您可以将 MyBase
变成 class 和 将 M
和 T
变成类型参数(而不是抽象类型),那么至少有一个解决方案。
正如我在另一个相关问题 (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
实例。