Scala: Restricting traits mixing-in f-bounded polymorphic traits

156 Views Asked by At

I have:

trait Pet[T <: Pet[T]] {    //disallows: Dog extends Pet[String]
  self: T =>                //disallows: Dog extends Pet[Monkey]
  def rename(s: String): T
  def name: String
}

Now a trait like Feline that would extend the Pet class can be easily added as follows:

trait Feline[T <: Feline[T]] extends Pet[T] {
  self: T =>
  def pur : Unit = println("purrrr")
  def scratch: Unit
}

But if I were to introduce a type mixing in Pet with a self-type such as:

trait PetCareInfo[T <: PetCareInfo[T]] {
    self: T with Pet[T] =>
    def registerPet: Unit
  }

I get the error:

type arguments [T] do not conform to trait Pet's type parameter bounds [T <: Pet[T]]

my understanding is that this is because self-type check in PetCareInfo looks at the types A with B individually and as such fails the restriction. (not sure if this is a bug or feature)

I can use existential types instead:

type TypeRestriction: Pet[A] forSome {type A}

trait PetCareInfo[T <: PetCareInfo[T]] {
    self: T with TypeRestriction => //mix-in restriction
    def registerPet: Unit
  }

and that would kinda work. Two questions:

  1. I cannot directly define the the existential type at the mix-in restriction line. I get:

; expected but 'forSome' found.

Is there a way of getting around this?

  1. In practice the PetCareInfo's forSome restriction + Pet's own restriction means that I can not have:

    class Cat extends Pet[Dog] with PetCareInfo[Cat]

But I would like to know if there is a way of not depending on Pet for this.

Update:

For question 2, I can change the existing type restriction to be:

type Restriction[T] = A with Pet[A] forSome {type A <: PetCareInfo[T]}

trait PetCareInfo[T <: PetCareInfo[T]] {
  self: Restriction[T] =>
  def registerPet: Unit
}

and that seems to be solving the problem. Although, there is still no guarantee that the structural A type will be the same as T, so we are still depending on Pet. :(

1

There are 1 best solutions below

13
On BEST ANSWER

Try this:

trait PetCareInfo[T <: Pet[T] with PetCareInfo[T]] {
  self: T =>
  def registerPet: Unit
}

abstract class Cat extends Feline[Cat] with PetCareInfo[Cat] // OK
abstract class Dog extends Pet[Dog] with PetCareInfo[Dog] // OK
abstract class Tiger extends Feline[Tiger] with PetCareInfo[Cat] // Error.

Update: The above demonstrates an is a relationship. That is, Cat both is a Feline and is a PetCareInfo. Here's an alternative that makes PetCareInfo a member of Pet, so that a Cat has a PetCareInfo. (I'm assuming that this makes sense. You could equally have a Pet member of PetCareInfo if that's more appropriate.)

// Change of emphasis: T is type of Pet. OK since we're in charge of its definition.
trait PetCareInfo[T <: Pet[T]] {
  // Etc.
}

trait Pet[T <: Pet[T]] {
  // Etc.
  val info: PetCareInfo[T]
}

abstract class Dog extends Pet[Dog] {
  // Etc.
  override val info = new PetCareInfo[Dog] {
     // Define a what a PetCareInfo looks like for a dog.
  }
}

This latter approach could also be used to hide PetCareInfo (if the info member was private), in the event that such details are not useful to the code user.

UPDATE 2: BTW, regarding the error "type arguments [T] do not conform to trait Pet's type parameter bounds [T <: Pet[T]]" for:

trait PetCareInfo[T <: PetCareInfo[T]] {
  self: T with Pet[T] => // <- Error
  def registerPet: Unit
}

the message is self-explanatory: Pet's T must be derived from Pet[T]; however, you have only defined a T for PetCareInfo to be derived from PetCareInfo[T], and a PetCareInfo[T] has no expressed relationship to a Pet[T]. The self declaration simply constrains the type of any concrete PetCareInfo instance, and cannot be used to change the definition of what a T represents.

That is, T must be derived from PetCareInfo[T] and self must belong to an object that extends T with a Pet[T]. However, since T is not derived from Pet[T], it's impossible to create such an instance, hence the error. So, it's not a bug but an essential type check.