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 andComponent
s - maybe a more concrete example (e.g. 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:but this is just a convenience thing. You could also turn some of your
trait
s intoabstract 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 yourval
s withdef
s, or by using early initializers.