1
votes

I have this scala code:

class Creature {
  override def toString = "I exist"
}

class Person(val name: String) extends Creature {
  override def toString = name
}

class Employee(override val name: String) extends Person(name) {
  override def toString = name
}

class Test[T](val x: T = null) {

  def upperBound[U <: T](v: U): Test[U] = {
    new Test[U](v)
  }

  def lowerBound[U >: T](v: U): Test[U] = {
    new Test[U](v)
  }
}

We can see the hierarchy relationship between Creature, Person, and Employee:

Creature <- Person <- Employee

In the def main:

val test = new Test[Person]()

val ub = test.upperBound(new Employee("John Derp")) //#1 ok because Employee is subtype of Person
val lb = test.lowerBound(new Creature())            //#2 ok because Creature is supertype of Person

val ub2 = test.upperBound(new Creature())           //#3 error because Creature is not subtype of Person
val lb2 = test.lowerBound(new Employee("Scala Jo")) //#4 ok? how could? as Employee is not supertype of Person

What can I understand is:

  1. A <: B define A must be subtype or equal to B (upper bound)

  2. A >: B define A must be supertype or equal to B (lower bound)

But what happened to #4 ? Why there is no error? As the Employee is not supertype of Person, I expect it shouldn't conform to the bound type parameter [U >: T].

Anyone can explain?

2

2 Answers

2
votes

This example may help

scala> test.lowerBound(new Employee("Scala Jo"))
res9: Test[Person] = Test@1ba319a7

scala> test.lowerBound[Employee](new Employee("Scala Jo"))
<console>:21: error: type arguments [Employee] do not conform to method lowerBound's type parameter bounds [U >: Person]
              test.lowerBound[Employee](new Employee("Scala Jo"))
                             ^

In general, it's connected to the Liskov Substitution Principle - you can use subtype anywhere instead of supertype (or "subtype can always be casted to its supertype"), so type inference trying to infer as nearest supertype as it can (like Person here).

So, for ub2 there is no such intersection between [Nothing..Person] and [Creature..Any], but for lb2 there is one between [Person..Any] and [Employee..Any] - and that's the Person. So, you should specify type explicitly (force Employee instead of [Employee..Any]) to avoid type inference here.

The example where lb2 expectedly failing even with type inference:

scala> def aaa[T, A >: T](a: A)(t: T, a2: A) = t
aaa: [T, A >: T](a: A)(t: T, a2: A)T

scala> aaa(new Employee(""))(new Person(""), new Employee(""))
<console>:19: error: type arguments [Person,Employee] do not conform to method aaa's type parameter bounds [T,A >: T]
              aaa(new Employee(""))(new Person(""), new Employee(""))
              ^

Here type A is inferred inside first parameter list and fixated as Employee, so second parameter list (which throws an error) just have only choice - to use it as is.

Or most commonly used example with invariant O[T]:

scala> case class O[T](a: T)
defined class O

scala> def aaa[T, A >: T](t: T, a2: O[A]) = t
aaa: [T, A >: T](t: T, a2: O[A])T

scala> aaa(new Person(""), O(new Employee("")))
<console>:21: error: type mismatch;
 found   : O[Employee]
 required: O[Person]
Note: Employee <: Person, but class O is invariant in type T.
You may wish to define T as +T instead. (SLS 4.5)
              aaa(new Person(""), O(new Employee("")))
                                   ^

T is fixed to Employee here and it's not possible to cast O[Employee] to O[Person] due to invariance by default.

1
votes

I think it happens because you can pass any Person to your

Test[Person].lowerBound(Person)

and as Employee is a subclass of Person it is considered as Person which is legal here.