简体   繁体   中英

ClassTag and path-dependent types in a cake-pattern-like flavour

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM