Search code examples
scalalower-boundupperbound

Scala lower bound does not behave as I expect


Today I spent a few hours to understand the logic behind Lower Bounds in Scala but the more I read the more confusing it gets. Would you please shed some light on this?

Here is the simple class hierarchy for our workshop:

class Animal
class Pet extends Animal
class Wild extends Animal

class Dog extends Pet
class Cat extends Pet

class Lion extends Wild
class Tiger extends Wild

So the hierarchy would be something like:

        Animal
        /    \
      Pet    Wild
      / \    /  \
    Dog Cat Lion Tiger

And here is the client code:

 object Main extends App {
  //I expect the compilation of passing any type above Pet to fail
  def upperBound[T <: Pet](t: T) = {println(t.getClass.getName)}

  //I expect the compilation of passing any type below Pet to fail
  def lowerBound[T >: Pet](t: T) = {println(t.getClass.getName)}

  upperBound(new Dog)//Ok, As expected
  upperBound(new Cat)//Ok, As expected
  upperBound(new Pet)//Ok, As expected
  //Won't compile (as expected) because Animal is not a sub-type of Pet
  upperBound(new Animal)

  lowerBound(new Pet)//Ok, As expected
  lowerBound(new Animal)//Ok, As expected
  //I expected this to fail because Dog is not a super type of Pet
  lowerBound(new Dog)
  //I expected this to fail because Lion is not a super type of Pet either
  lowerBound(new Lion)
  lowerBound(100)//Jesus! What's happening here?!
  lowerBound(Nil)// Ok! I am out!!! :O
}

Well... The last four lines of the code does not make any sense to me! From what I understand, Lower Bound does not impose any constraints on the type parameter at all. Is there an implicit Bound to Any or AnyRef somewhere which I am missing?


Solution

  • Let me explain the unexpected behaviour of bounded type inference

    1. Upper Bound(T <: Pet): This means T is applicable for all the classes which has inherited at least the Pet class or any of Pet's subclasses.
    2. Lower Bound(T >: Pet): This means T is applicable for all the classes which has inherited at least one of the parent class of Pet class.

    So as you correctly guessed, AnyRef is a super type of all object/reference types. So when we say

    lowerBound(new Dog())
    

    Dog is under the class AnyRef. So by lower bound, since AnyRef is a parent of Pet, the compiler throws no warning.

    You can see similar behaviour for the :: method of scala List class. With List you can do the following without any compile errors.

    val list = List(1, 2, 3)
    
    val newList = "Hello" :: list
    

    For further reading, please take a look at these stack overflow answers:

    1. https://stackoverflow.com/a/19217523/4046067
    2. https://stackoverflow.com/a/19821995/4046067