Question

I've defined some case classes that all have a field id: Id[T], where T is the type of the case class. I would like to have a trait enforcing this property to write generic code on these classes.

My first try uses a type parameter and a self type on a trait to enforce this property:

case class Id[M](value: Long)

trait EnforceIdType[T] {
  this: T =>
  val id: Id[T]
}

case class A(id: Id[A]) extends EnforceIdType[A]
case class B(id: Id[B]) extends EnforceIdType[B]
// case class C(id: Id[B]) extends EnforceIdType[B] < Won't compile, as expected

This is fine but I wonder if there is a way to do the same without using a type parameter on the trait. Here is my second try:

case class Id[M](value: Long)

trait EnforceIdType {
  val id: Id[_ <: EnforceIdType]
}

case class A(id: Id[A]) extends EnforceIdType
case class B(id: Id[B]) extends EnforceIdType
case class C(id: Id[B]) extends EnforceIdType // Compiles :(

Concretely, I would like to use these definitions of case classes (without passing the type parameter to EnforceIdType) but still enforce the property (this C should not compile). Is there any way to do this? I feel like something like this.type could be used here but I'm not very familiar with it.

Was it helpful?

Solution

You are right, this.type need to be used, but with some limitation:

trait EnforceIdType {
  def id: Id[_ >: this.type <: EnforceIdType]
}

case class A(id: Id[A]) extends EnforceIdType
case class B(id: Id[B]) extends EnforceIdType
//case class C(id: Id[B]) extends EnforceIdType // Won't compile, as expected

Updated:

About second limitation (shown by Alexey Romanov). It can be eliminated but with long way:

//Embeded polymorphism used
class Id(val value: Long) {
  type M
}

// Factory method for shift type variable to type parameter field
object Id {
  def apply[T](value : Long) = new Id(value) { type M = T }
}

trait EnforceIdType {
  type This = this.type // store this.type for use inside Id { type M = .. }
  val id: Id { type M >: This <: EnforceIdType } // need to be val

  def show(x : id.M) { println (x) } // dependent method
}

case class A(id: Id { type M = A }) extends EnforceIdType
case class B(id: Id { type M = B }) extends EnforceIdType
//  case class C(id: Id { type M = B }) extends EnforceIdType // Won't compile, as expected

val a = A( Id[A](10))
val b = B( Id[B](10))

a.show(a)
// a.show(b) // Won't compile, as expected
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top