Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Option type upper bound don't understand

I'm reading Functional Programming in Scala, and in chapter 04 the authors implement Option on their own. Now, when defining the function getOrElse they use an upper bound to restrict the type of A to a supertype (if a understood correctly)

So, the definition goes:

sealed trait Option[+A] {
   def getOrElse[B >: A](default: => B): B = this match {
     case None => default
     case Some(a) => a
   }
}

So, when we have something like

val a = Some(4)
println(a.getOrElse(None)) => println prints a integer value
val b = None
println(b.getOrElse(Some(3)) => println prints a Option[Integer] value

a has type Option[Int], so A would be type Int. B would be type Nothing. Nothing is a subtype of every other type. That means that Option[Nothing] is a subtype of Option[Int] (because of covariance), right?

But with B >: A we said that B has to be a supertype?! So how can we get an Int back? This is a bit confusing for me...

Anyone care to try and clarify?

like image 818
Marin Avatar asked Apr 27 '15 20:04

Marin


Video Answer


1 Answers

That means that Option[Nothing] is a subtype of Option[Int] (because of covariance), right?

Correct. Option[Nothing] is an Option[Int].

But with B >: A we said that B has to be a supertype?! So how can we get an Int back?

It doesn't have to be a super-type. It just requires A as a lower-bound. Which means you can still pass Int to getOrElse if A is Int.

But that doesn't mean you can't pass instances of a sub-class. For instance:

class A
class B extends A
class C extends B

scala> Option(new B)
res196: Option[B] = Some(B@661f82ac)

scala> res196.getOrElse(new C)
res197: B = B@661f82ac

scala> res196.getOrElse(new A)
res198: A = B@661f82ac

scala> res196.getOrElse("...")
res199: Object = B@661f82ac

I can still pass an instance of C, because C can be up-cast to B. I can also pass a type higher up the inheritance tree, and getOrElse will return that type, instead. If I pass a type that has nothing to do with the type contained in the Option, then the type with the least upper-bound will be inferred. In the above case, it's Any.


So why is the lower-bound there at all? Why not have:

def getOrElse[B <: A](default: => B): B

This won't work because getOrElse must either return the A that's contained in the Option, or the default B. But if we return the A, and A is not a B, so the type-bound is invalid. Perhaps if getOrElse returned A:

def getOrElse[B <: A](default: => B): A

This would work (if it were really defined that way), but you would be restricted by the type-bounds. So in my above example, you could only pass B or C to getOrElse on an Option[B]. In any case, this is not how it is in the standard library.


The standard library getOrElse allows you to pass anything to it. Say you have Option[A]. If we pass a sub-type of A, then it is up-cast to A. If we pass A, obviously this is okay. And if we pass some other type, then the compiler infers the least upper-bound between the two. In all cases, the type-bound B >: A is met.

Because getOrElse allows you to pass anything to it, many consider it very tricky. For example you could have:

val number = "blah"
// ... lots of code
val result = Option(1).getOrElse(number)

And this will compile. We'll just have an Option[Any] that will probably cause an error somewhere else down the line.

like image 132
Michael Zajac Avatar answered Oct 21 '22 12:10

Michael Zajac