ClassTag and path-dependent types in a pie-like flavor
I am working on a small 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 has nothing to do with taking a picture.
I don't have much experience with dependent types, in my case I have the following trait which I use to abstract 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 snippet of my (artificial) cake template:
trait BaseComponent {
type ComponentTypes <: Types
val a: Types#A
implicit val bTag: ClassTag[Types#B]
}
Then I have the actual implementation of my component, which can be seen like this:
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 a tag because later it will need a service (in my actual implementation, I use this type to abstractly express for different types of exceptions thrown by different DB libraries); I'm pretty sure there is a much better way to do what I am trying to do.
If I don't instantiate ComponentTypes
to get the tag, and I move the implicit collaboration code to the DefaultBaseComponent, it calls null
instead ClassTag
. I need to be able to reference the actual types that I am using (different A
and B
that I have in different environments), and I need to do this in other components without knowing what actual types they are.
My solution works, compiles and passes all the tests I wrote for it, can anyone help me improve it?
Thank!
source to share
Your example is a little unclear with all of these Default
and Component
- maybe a more specific example (like DatabaseService / MysqlDatabaseService) will become clearer?
You need to pass ClassTag
around wherever it is abstract - you can only "call" it when you have a concrete type. You might want to structure the concept 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 it's just a handy thing. You can also turn some of yours trait
into abstract class
es so you can use them [A: ClassTag]
to pass tags implicitly, but obviously this affects which classes you can inherit.
If you click null
it sounds like an issue with the traits initialization order, although without much error message it's tricky. You may be able to fix this problem by replacing some of yours val
with def
s or by using early initializers.
source to share