Type inference for collections of objects implementing classes with (self) type paramenters
-
03-06-2021 - |
سؤال
Consider the following class definitions:
class Person[+T <: Person[T]]
class Student() extends Person[Student]
class Professor() extends Person[Professor]
I would like a list with a student and a professor:
val persons = List(new Student(), new Professor())
But this fails to compile with the following error:
type arguments [Person[Person[Any]]] do not conform to class Person's type parameter bounds [+T <: Person[T]]
Thanks to Daniel C. Sobral's answer to my related previous question How to define case classes with members with unbound type parameters? I know an existential type would do the trick here. This compiles:
val persons = List[Person[T] forSome {type T <: Person[T]}](new Student(), new Professor())
The problem is caused by the upper-bound <: Person[T]
in the type parameter of the class Person's declaration. Removing the upper-bound lets the compiler infer a type parameter for the list which makes it compile: List[Person[Person[Person[Any]]]]
as far as I can see.
Questions
- Why can't the compiler infer any type for the list which would make it compile?
- Existential types are to the least verbose and might furthermore be tricky (see Daniel's answer to my previous question linked above): Is there an alternative to explicit existential types for creating a list of students and professors?
المحلول
I believe you've mentioned a possible alternative yourself in your second comment
val persons = List[Person[_]](new Student(), new Professor())
But from my understanding, the idiomatic way in Scala for doing things like this is to use a type declaration in Person and have it defined in Student and Professor:
trait Person {
type PersonImpl <: Person
def colleagues: Seq[PersonImpl]
}
class Student extends Person {
type PersonImpl = Student
def colleagues = Seq(this)
}
class Professor extends Person {
type PersonImpl = Professor
def colleagues = Seq(this)
}
val persons = List(new Student, new Professor)
Martin Odersky also mentioned in scala-language, that he's thinking about unifying type parameters and abstract type members.
Dependent on your actual use case, the simplest solution may be to rely on method overriding:
trait Person {
def colleagues: Seq[Person]
}
class Student extends Person {
def colleagues: Seq[Student] = Seq(this)
}
class Professor extends Person {
def colleagues: Seq[Professor] = Seq(this)
}
val persons = List(new Student, new Professor)