I am working on a slick project and I am trying to make my database layer easily swappable between different profiles in order to write tests on an in-memory database. This question is inspired by this problem but it doesn't have anything to do with slick itself.
I don't have a great deal of experience with dependent types, in my case I have the following trait that I use to abstract away some types from the database:
trait Types {
type A <: SomeType
type B <: SomeOtherType
val bTag: ClassTag[B]
}
Then I have another trait which is basically a slice of my (faux) cake pattern:
trait BaseComponent {
type ComponentTypes <: Types
val a: Types#A
implicit val bTag: ClassTag[Types#B]
}
Then I have an actual implementation of my component that can be seen as follows:
trait DefaultTypes {
type A = SomeConcreteType
type B = SomeOtherConcreteType
val bTag = implicitly[ClassTag[B]]
}
trait DefaultBaseComponent extends BaseComponent {
type ComponentTypes = DefaultTypes
val ct = new ComponentTypes {}
implicit val bTag = ct.bTag
}
I need the tag because later on a service will need it (in my actual implementation I use this type to abstract over different type of exceptions thrown by different DB libraries); I am quite sure that there is a much better way to do what I am trying to do.
If I do not instantiate the ComponentTypes
trait in order to get the tag and I move the implicit-conjuring code in the DefaultBaseComponent it will conjure a null
in place of the ClassTag
. I need to have a way to refer to the actual types that I am using (the different A
and B
that I have in my different environments) and I need to do it in other components without knowing which actual types they are.
My solution works, compiles and pass all the tests I wrote for it, can anyone help me in getting it better?
Thank you!
Your example is a bit unclear with all these Default
s and Component
s - maybe a more concrete example (eg DatabaseService / MysqlDatabaseService) would make it clearer?
You need to pass the ClassTag
around wherever it's abstract - you can only "summon" one when you have a concrete type. You might like to package up the notion of a value and its tag:
trait TaggedValue[A] {val a: A; val ct: ClassTag[A]}
object TaggedValue {
def apply[A: ClassTag](a1: A) =
new TaggedValue[A] {
val a = a1
val ct = implicitly[ClassTag[A]]
}
}
but this is just a convenience thing. You could also turn some of your trait
s into abstract class
es, allowing you to use [A: ClassTag]
to pass the tags implicitly, but obviously this affects which classes you can multiply inherit.
If you're hitting null
s that sounds like a trait initialization order problem, though without a more specific error message it's hard to help. You might be able to resolve it by replacing some of your val
s with def
s, or by using early initializers.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.