1

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?

dk14
  • 22,206
  • 4
  • 51
  • 88
null
  • 8,669
  • 16
  • 68
  • 98

2 Answers2

2

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.

dk14
  • 22,206
  • 4
  • 51
  • 88
1

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.

user5102379
  • 1,492
  • 9
  • 9
  • But lowerbound `U >: T` indicate it expects U to be same type or supertype to T. In the case #4, T = Person, U = Employee. `Employee` is subtype of `Person` so it shouldn't get passed, or am I wrong? – null Jan 19 '15 at 12:40
  • 2
    `test.lowerBound[Employee](new Employee("Scala Jo")` does not work. `test.lowerBound[Person](new Employee("Scala Jo")` works. Your code works because the compiler uses `Person` in `U` instead of `Employee` – planetenkiller Jan 19 '15 at 13:18
  • @planetenkiller: so the `U` is actually coming from the class type `[Person]` instead of `Employee` type from `new Employee("Scala Jo")` argument? – null Jan 19 '15 at 13:28
  • U is coming from method. As @planetenkiller wrote compiler uses `test.lowerBound[Person](Person)`. U is a method type parameter with a restriction. – user5102379 Jan 19 '15 at 14:14
  • So U is coming from method type parameter where if I don't specify the type parameter on the method, it will take from the class type parameter instead, is my understanding correct? – null Jan 19 '15 at 16:04
  • Yes. If You don't specify type parameter, i.e. `def lowerBound(v: T): Test[T]` in `Test[T]` then this method for `Test[Person]` will accept only `Person` or derived. If you specify `def lowerBound[T](v: T): Test[T]` in `Test[T]` then you can use arg of any type in `Test[Person].lowerBound`, for example `new Test[Person].lowerBound("string")`, because method `T` will hide class `T`. – user5102379 Jan 19 '15 at 17:30
  • If that's the case, the `test.upperBound(new Creature())` should also succeed too because I don't specify type parameter in the method, so the `U` should be `Person` too instead of `Creature`. – null Jan 21 '15 at 08:16
  • No. It will not work, because `Creature` is not a `Person` and compiler disallows this. – user5102379 Jan 21 '15 at 08:30
  • I'm still confused about the process. So the `[U >: T]` determines the `U` type in `(v: U)`. In the example above, I don't specify the type for the methods, so all of them are implicitly having method type `Person`, e.g. `test.lowerBound[Person](..)`. In #4, I can understand Employee is a (subtype of) Person so it's valid, so does the #3 it's not valid because Creature is not subtype of Person. But how about the case #2 `test.lowerBound(new Creature())`? `Creature` is not a `Person`, but it can pass. How to explain that? – null Jan 21 '15 at 10:26
  • "so all of them are implicitly having method type Person" - No. `Person` is only a restriction for `U`. "so does the #3 it's not valid because Creature is not subtype of Person" - Yes. "But how about the case #2" - Here `U` becomes `Creature` and it satisfies to `U >: T`, i.e. `Creature >: Person` so it passes. – user5102379 Jan 21 '15 at 10:38
  • I'm confused again ^^, you said `Person is only a restriction for U`, but you also said `Here U becomes Creature`. Isn't it the case #2 shouldn't pass because `U` (`Creature`) is not `Person` (because of the restriction of U to Person), then the case #4 shouldn't pass because `U` become `Employee` and it won't satisfy the `U >: T` (`Employee >: Person`) – null Jan 21 '15 at 11:09
  • "Isn't it the case #2 shouldn't pass" - It should pass because `Creature >: Person`. `U` should be equal or greater than `Person`. "then the case #4 shouldn't" - here compiler sees that it can satisfy the restiriction `U >: Person` if it upcast `Employee` to `Person` (it is always possible). So in this case `U` becomes `Person` and `Person >: Person` so it is OK. – user5102379 Jan 21 '15 at 11:25