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.

Était-ce utile?

La 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
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top