问题描述
我有这个基本特征
trait MyBase {M型类型 T
其中 TableQuery
是 scala.slick.lifted.TableQuery
我的子类像这样实例化TableQuery
:
type M = 账户类型 T = AccountsTableval 查询 = TableQuery[T]
我想在基本特征中实例化TableQuery
,可能通过使用lazy val
,即
懒惰的val查询:TableQuery[T] = {...}
我一直在玩反射,但运气不佳.
如果我理解正确,你想要的是能够扩展MyBase
通过简单地定义 M
和 T
而不必在每个派生类中显式实例化 TableQuery
.>
使用反射并不是一种真正的选择,因为通常您使用 TableQuery.apply
为此(如 val query = TableQuery[MyTable]
),这是通过宏实现的,所以你有一个运行时与编译时"的问题.
如果您绝对需要 MyBase
成为特征(而不是类),那么我看不到任何可行的解决方案.但是,如果您可以将 MyBase
转换为类 并 将 M
和 T
转换为类型参数(而不是抽象类型)),那么至少有一个解.正如我在另一个相关问题中所暗示的(How to define generic type inScala?),你可以定义一个类型类(比如 TableQueryBuilder
)来捕获对 TableQuery.apply
的调用(在具体类型已知的点)以及一个隐式宏(比如 TableQueryBuilder.builderForTable
) 提供此类型类的实例.然后,您可以定义一个方法(比如 TableQueryBuilder.build
)来实际实例化 TableQuery
,它只会将作业委托给类型类.
//注意:使用 Scala 2.11.0 & 测试光滑 3.0.0导入 scala.reflect.macros.Context导入 scala.language.experimental.macros对象 TableQueryBuilderMacro {def createBuilderImpl[T<:AbstractTable[_]:c.WeakTypeTag](c: Context) = {导入 c.universe._val T = weakTypeOf[T]q"""new TableQueryBuilder[$T]{def apply(): TableQuery[$T] = {表查询[$T]}}"""}}trait TableQueryBuilder[T<:AbstractTable[_]] {def apply(): TableQuery[T]}对象 TableQueryBuilder {隐式 def builderForTable[T<:AbstractTable[_]]: TableQueryBuilder[T] = 宏 TableQueryBuilderMacro.createBuilderImpl[T]def build[T<:AbstractTable[_]:TableQueryBuilder](): TableQuery[T] = 隐式[TableQueryBuilder[T]].apply()}
最终效果是您不再需要知道 T
类型的具体值,以便能够实例化 TableQuery[T]
,前提是您在范围内有一个 TableQueryBuilder[T]
的隐式实例.也就是说,你可以转移需要知道T
的具体值直到您真正了解为止.
MyBase
(现在是一个类)可以这样实现:
class MyBase[M, 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 * =(名称,价格)}class Derived extends MyBase[(String, Double), Coffees]//就是这样!
这里发生的事情是,在Derived
的构造函数中,TableQueryBuilder[Coffees]
的隐式值是隐式的传递给 MyBase
的构造函数.
如果 MyBase
是一个 trait,你不能应用这个模式的原因很普通:trait 构造函数不能有参数,更不用说隐式参数了,所以没有隐式方法传递 TableQueryBuilder
实例.
I have this base trait
trait MyBase {
type M
type T <: Table[M]
val query: TableQuery[T]
}
Where TableQuery
is scala.slick.lifted.TableQuery
My subclasses instantiate TableQuery
like so:
type M = Account
type T = AccountsTable
val query = TableQuery[T]
I'd like to instantiate the TableQuery
in the base trait, possibly by using a lazy val
, i.e.
lazy val query: TableQuery[T] = {
...
}
I've been playing around with reflection, but haven't had much luck.
If I understand correctly, what you want is to be able to extend MyBase
by simply defining M
and T
but without having to explicitly instantiate the TableQuery
in each derived class.
Using reflection is not really an option because normally you use TableQuery.apply
for that (as in val query = TableQuery[MyTable]
), and this is implemented through a macro, so you've got a "runtime vs compile-time" issue.
If you absolutely need MyBase
to be a trait (as opposed to a class), then I don't see any viable solution.However if you can turn MyBase
into a class and turn M
and T
into type parameters (instead of abstract types), then there is at least one solution.As I hinted in another related question (How to define generic type in Scala?), you candefine a type class (say TableQueryBuilder
) to capture the call to TableQuery.apply
(at the point where the concrete type is known) along with an implicit macro (say TableQueryBuilder.builderForTable
) to provide an instance of this type class. You can then define a method (say TableQueryBuilder.build
) to actually instantiate the TableQuery
, which will just delegate to job to the type 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()
}
The net effect is that you don't need anymore to know the concrete value of the type T
in order to be able to instantiate a TableQuery[T]
, provided that you have an implicit instance of TableQueryBuilder[T]
in scope. In other words, you can shift the need to know the concrete value of T
up to the point where you actually know it.
MyBase
(now a class) can then be implemented like this:
class MyBase[M, T <: Table[M] : TableQueryBuilder] {
lazy val query: TableQuery[T] = TableQueryBuilder.build[T]
}
And you can then extend it without the need to explcitly call 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!
What happens here is that in Derived
's constructor, an implicit value for TableQueryBuilder[Coffees]
is implicitly passed to MyBase
's constructor.
The reason why you cannot apply this pattern if MyBase
were a trait is pretty mundane: trait constructors cannot have parameters, let alone implicit parameters, so there would be no implicit wayto pass the TableQueryBuilder
instance.
这篇关于Scala 反射实例化 scala.slick.lifted.TableQuery的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!