3
votes

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.

1

1 Answers

4
votes

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 of B.

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.