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:
; expected but 'forSome' found.
Is there a way of getting around this?
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
. :(
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.