سؤال

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

  1. Why can't the compiler infer any type for the list which would make it compile?
  2. 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)
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top