Reputation: 812
I'm trying to create a generic trait 'Repo' for some types that are subtypes of a trait 'Identifiable'. My plan is to instantiate implementors of 'Repo' by passing a generic TypeTag[HList] that describes the 'Identifiable'-subtypes.
How can I make the compiler guarantee that the types passed in the HList are subtypes of trait 'Identifiable'?
Here's what I've got so far:
//All types in HList must extend Identifiable, how to enforce that at compile time?
trait Repo {
implicit val ltag: TypeTag[L] forSome {type L <: HList}
..
}
trait Identifiable {
..
}
case class Person(..) extends Identifiable
case class Address(..)
//This should compile
class MyRepo
(implicit val ltag: TypeTag[Person :: HNil])
extends Repo {
..
}
//This should not
class MyRepo
(implicit val ltag: TypeTag[Address :: HNil])
extends Repo {
..
}
//HList can contain an unknown number of types
I've seen this question which seems to be related: Type inference on contents of shapeless HList Difference is I don't have an implementation of the HList to work with so not sure how I can calculate the upper bound with types only.
Upvotes: 4
Views: 544
Reputation: 108101
There's a whole set of constrains on HList provided by https://github.com/milessabin/shapeless/blob/master/core/src/main/scala/shapeless/hlistconstraints.scala.
The one you're after is probably LUBConstraint
. Quoting the documentation:
Type class witnessing that every element of
L
is a subtype ofB
.
To use, you just need to require implicit evidence of a LUBContraint[L, Identifiable]
.
E.g.
trait Repo[L <: HList] {
implicit val ltag: TypeTag[L]
implicit val ev: LUBConstraint[L, Identifiable]
}
trait Identifiable
case class Person(name: String) extends Identifiable
case class Address(street: String)
type P = Person :: HNil
class MyPersonRepo(implicit
val ltag: TypeTag[P],
val ev: LUBConstraint[P, Identifiable]
) extends Repo[P]
type A = Address :: HNil
class MyAddressRepo(implicit
val ltag: TypeTag[A],
val ev: LUBConstraint[A, Identifiable]
) extends Repo[A]
new MyPersonRepo // this works
new MyAddressRepo // this doesn't
If you are willing to use an abstract class instead of a trait, you can make everything nicer
abstract class Repo[L <: HList](implicit
val ltag: TypeTag[L],
val ev: LUBConstraint[L, Identifiable]
)
trait Identifiable
case class Person(name: String) extends Identifiable
case class Address(street: String)
type P = Person :: HNil
class MyPersonRepo extends Repo[P]
type A = Address :: HNil
class MyAddressRepo extends Repo[A]
Now you'll get the error right away when extending the class.
Upvotes: 4